Kean Walmsley


  • About the Author
    Kean on Google+

August 2014

Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            








« Allowing a user to select from multiple file formats inside AutoCAD using .NET | Main | Gluing a point to an AutoCAD curve using overrules from .NET – Part 2 »

August 24, 2009

Gluing a point to an AutoCAD curve using overrules from .NET – Part 1

Over the weekend I put together a little prototype to prove a concept for an internal project I’m working on. The idea was to force a point onto a curve (meaning anything inheriting from Curve in AutoCAD, such as Arc, Circle, Ellipse, Leader, Line, Polyline, Polyline2d, Polyline3d, Ray, Spline, Xline…), so that when the point is moved it snaps onto the curve to which it’s assigned. The solution I’ve put together is far from being complete – which is partly why I’m planning on making this a series, so I can flesh it out a little further in further posts – but it does demonstrate a reasonable technique for addressing the requirement.

The approach I chose was to use a TransformOverrule to modify the standard AutoCAD point’s TransformBy() behaviour (see this previous post for some commentary on using a TransformOverrule vs. a GripOverrule). Our TransformOverrule stores a list of curves that have had points attached to them, and during TransformBy() we check each one to see which curve this point was on. We then get the transformed point (i.e. the one being chosen by the user) and from there we get the closest point on that curve, which becomes the point’s new location.

TransformBy() is a pretty handy operation to overrule: it’s used by grip-editing and by the MOVE command, so you know these operations will lead to your object’s positional integrity being maintained (direct modification of properties, such as via the Properties Palette, won’t lead to it being called, however, so it’s not enough if you need to maintain complete control).

Some comments on the choice of storing a list of curves rather than some other association between the point and the curve:

  • Storing a map between the point (via its ObjectId) and the curve wouldn’t work, as TransformBy() often has to work on a temporary copy of an object, rather than the object itself (and the copy’s ObjectId will therefore be Null).
  • It might be possible to attach data (perhaps XData) to the point identifying the curve it’s attached to, but this would need to be available on the temporary clone (and is something that I’d need to check works).
  • It’s possible that working through a list of curves, checking each one, could become perceptibly slow if working with huge sets of data, but at that point a more efficient spatial indexing technique could be adopted.
  • Using a list of curves also allows us to modify the implementation to allow a point to travel along a network of curves (we’ll go through this modification in a future post).
  • One drawback of this approach is that once we move the curve independently from the point, they become detached as the point is no longer on any of the curves. We’ve put a specific clause in to allow points not on curves to be moved, but it would also be good to have the point be transformed along with the curve, so they stay together (another potential future modification). In the meantime the user will have to move the point back onto the curve (using the NEAr object snap, to make sure it’s precise) for the overrule to work for it, again.

One last point: we’re not persisting the curve list, in any way, so don’t expect points to reattach to lines when a session is restarted (until the POC command is used to create further points on curves and therefore add the curves to the list of those being managed by our system).

OK, enough blather, let’s get on with looking at the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

 

namespace PointOnCurveTest

{

  public class PtTransOverrule : TransformOverrule

  {

    // A static pointer to our overrule instance

 

    static public PtTransOverrule theOverrule =

      new PtTransOverrule();

 

    // A list of the curves that have had points

    // attached to

 

    static internal List<ObjectId> _curves =

      new List<ObjectId>();

 

    // A flag to indicate whether we're overruling

 

    static bool overruling = false;

 

    public PtTransOverrule() {}

 

    // Out primary overruled function

 

    public override void TransformBy(Entity e, Matrix3d mat)

    {

      // We only care about points

 

      DBPoint pt = e as DBPoint;

      if (pt != null)

      {

        Database db = HostApplicationServices.WorkingDatabase;

 

        // For each curve, let's check whether our point is on it

 

        bool found = false;

 

        // We're using an Open/Close transaction, to avoid problems

        // with us using transactions in an event handler

 

        OpenCloseTransaction tr =

          db.TransactionManager.StartOpenCloseTransaction();

        using (tr)

        {

          foreach (ObjectId curId in _curves)

          {

            DBObject obj = tr.GetObject(curId, OpenMode.ForRead);

            Curve cur = obj as Curve;

            if (cur != null)

            {

              Point3d ptOnCurve =

                cur.GetClosestPointTo(pt.Position, false);

              Vector3d dist = ptOnCurve - pt.Position;

              if (dist.IsZeroLength(Tolerance.Global))

              {

                Point3d pos =

                  cur.GetClosestPointTo(

                    pt.Position.TransformBy(mat),

                    false

                  );

                pt.Position = pos;

                found = true;

                break;

              }

            }

          }

          // If the point isn't on any curve, let the standard

          // TransformBy() do its thing

 

          if (!found)

          {

            base.TransformBy(e, mat);

          }

        }

      }

    }

 

    [CommandMethod("POC")]

    public void CreatePointOnCurve()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Ask the user to select a curve

 

      PromptEntityOptions opts =

        new PromptEntityOptions(

          "\nSelect curve at the point to create: "

        );

      opts.SetRejectMessage(

        "\nEntity must be a curve."

      );

      opts.AddAllowedClass(typeof(Curve), false);

 

      PromptEntityResult per = ed.GetEntity(opts);

 

      ObjectId curId = per.ObjectId;

      if (curId != ObjectId.Null)

      {

        // Let's make sure we'll be able to see our point

 

        db.Pdmode = 97;  // square with a circle

        db.Pdsize = -10; // relative to the viewport size

 

        Transaction tr =

          doc.TransactionManager.StartTransaction();

        using (tr)

        {

          DBObject obj =

            tr.GetObject(curId, OpenMode.ForRead);

          Curve cur = obj as Curve;

          if (cur != null)

          {

            // Out initial point should be the closest point

            // on the curve to the one picked

 

            Point3d pos =

              cur.GetClosestPointTo(per.PickedPoint, false);

            DBPoint pt = new DBPoint(pos);

 

            // Add it to the same space as the curve

 

            BlockTableRecord btr =

              (BlockTableRecord)tr.GetObject(

                cur.BlockId,

                OpenMode.ForWrite

              );

            ObjectId ptId = btr.AppendEntity(pt);

            tr.AddNewlyCreatedDBObject(pt, true);

          }

          tr.Commit();

 

          // And add the curve to our central list

 

          _curves.Add(curId);

        }

 

        // Turn on the transform overrule if it isn't already

 

        if (!overruling)

        {

          ObjectOverrule.AddOverrule(

            RXClass.GetClass(typeof(DBPoint)),

            PtTransOverrule.theOverrule,

            true

          );

          overruling = true;

          TransformOverrule.Overruling = true;

        }

      }

    }

  }

}

Now let’s see what happens when we run the POC command (for Point On Curve, but then it’s also a Proof Of Concept – geddit? :-).

The POC command will create a point at the selected location on a curve, at which point we can grip-edit it:

Point on a curve

We can see that as we move the grip point around the drawing, the closest point to the attached curve is always used:

Point further along a curve

The point is always on the original curve, even if we try to select a point on another curve (even one that may be on the list of curves maintained by our PtTransOverrule class, if the POC command has been used on other curves):

But only on the attached curve

That’s enough to get us started. Later in the week we’ll look at extending the code to create a stronger attachment between the point and the curve, but also to allow the point to travel along a network of curves. Fun, fun, fun! :-)

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e20120a516674d970b

Listed below are links to weblogs that reference Gluing a point to an AutoCAD curve using overrules from .NET – Part 1:

blog comments powered by Disqus

10 Random Posts