Shortening a set of AutoCAD lines using .NET

Brent Burgess commented on this previous post showing how to extend lines in AutoCAD:

Is there a different process for shortening lines, or is it similar syntax?

A very valid question… unfortunately the Curve.Extend() method only works with points or parameters that are outside the current definition of the curve, so we need to find another way (if you try, you'll get an eInvalidInput, at least that's what I found :-).

I chose the approach of using Curve.GetSplitCurves() on each line, passing in an array of parameters at which to split the curve. I calculate the start parameter as 25% along the length, and the end parameter as being at the same point from the end. Which means each line will end up being half its original size. Lines are generally parameterised between 0 and 1, so we'll be passing an array containing 0.25 and 0.75 to the GetSplitCurves() method, which should consequently return an array of exactly three objects: lines from 0 to 0.25, 0.25 to 0.75 and from 0.75 to 1. We will therefore discard the first and last lines and use the middle one.

So what shall we do with this line? We could add it to the modelspace, but we really want it to replace the old one. We can use DbObject.HandOverTo() to replace the original line with this new one, retaining the original line's XData and Extension Dictionary. To make use of this function, we need to open the original line using an open/close transaction (as the "normal" type of transaction will cause the function to return eInvalidContext), and then call Dispose() on the original object, once done.

Here's the C# code that now contains commands to extend and shorten lines, with a shared function to handle the object selection:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

 

namespace GeometryExtension

{

  public class Commands

  {

    [CommandMethod("EXL")]

    public void ExtendLines()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Call a function to return a selection set of lines

 

      SelectionSet ss =

        SelectLines(ed, "\nSelect lines to extend: ");

      if (ss != null)

      {

        // Start our transaction

 

        Transaction tr =

          db.TransactionManager.StartTransaction();

        using (tr)

        {

          // Edit each of the selected lines

 

          foreach (SelectedObject so in ss)

          {

            // We're assuming only lines are in the selection-set

            // Could also use a more defensive approach and

            // use a dynamic cast (Line ln = xxx as Line;)

 

            Line ln =

              (Line)tr.GetObject(

                so.ObjectId,

                OpenMode.ForWrite

              );

 

            // We'll extend our line by a quarter of the

            // existing length in each direction

 

            Vector3d ext = ln.Delta / 4;

 

            // First the start-point

 

            ln.Extend(true, ln.StartPoint - ext);

 

            // And then the end-point

 

            ln.Extend(false, ln.EndPoint + ext);

          }

 

          // Mustn't forget to commit

 

          tr.Commit();

        }

      }

    }

 

    [CommandMethod("SHL")]

    public void ShortenLines()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Call a function to return a selection set of lines

 

      SelectionSet ss =

        SelectLines(ed, "\nSelect lines to shorten: ");

      if (ss != null)

      {

        // Start our transaction

        // (Needs to be Open/Close to avoid an eInvalidContext

        //  from HandOverTo())

 

        Transaction tr =

          db.TransactionManager.StartOpenCloseTransaction();

        using (tr)

        {

          // Edit each of the selected lines

 

          foreach (SelectedObject so in ss)

          {

            // We're assuming only curves are in the selection-set

            // Could also use a more defensive approach and

            // use a dynamic cast (Curve cur = xxx as Curve;)

 

            Curve cur =

              (Curve)tr.GetObject(

                so.ObjectId,

                OpenMode.ForWrite

              );

 

            double sp = cur.StartParam,

                  ep = cur.EndParam,

                  delta = (ep-sp) * 0.25;

 

            DoubleCollection dc = new DoubleCollection();

            dc.Add(sp + delta);

            dc.Add(ep - delta);

            DBObjectCollection objs = cur.GetSplitCurves(dc);

 

            if (objs.Count == 3)

            {

              Entity ent = (Entity)objs[1];

              cur.HandOverTo(ent, true, true);

              tr.AddNewlyCreatedDBObject(ent, true);

              cur.Dispose();

              objs[0].Dispose();

              objs[2].Dispose();

            }

          }

 

          // Mustn't forget to commit

 

          tr.Commit();

        }

      }

    }

 

    // Helper function to select a set of lines

 

    private SelectionSet SelectLines(Editor ed, string msg)

    {

      // We only want to select lines...

 

      // Use an options object to specify how the

      // selection occurs (in terms of prompts)

 

      PromptSelectionOptions pso =

        new PromptSelectionOptions();

      pso.MessageForAdding =

        (string.IsNullOrEmpty(msg) ? "\nSelect lines: " : msg);

 

      // Use a filter to specify the objects that

      // get included in the selection set

 

      TypedValue[] tvs =

        new TypedValue[1] {

            new TypedValue(

              (int)DxfCode.Start,

              "LINE"

            )

          };

      SelectionFilter sf = new SelectionFilter(tvs);

 

      // Perform our restricted selection

 

      PromptSelectionResult psr = ed.GetSelection(pso, sf);

 

      if (psr.Status != PromptStatus.OK || psr.Value.Count <= 0)

        return null;

 

      return psr.Value;

    }

  }

}

Let's see our re-factored EXL command working on a set of lines.

Before…

Lines

After…

Extended lines

Now when we shorten these same lines using the SHL command, we see them shrink back, but not quite to the same place (as 25% is relatively to the current length of the line, of course):

Reshortened lines

We could clearly extend this code to work with other curve types (I did test the shorten command to work with Arcs, for instance), but that's left as an exercise for the reader. If anyone has an approach they prefer to use for addressing this requirement, be sure to post a comment.

10 responses to “Shortening a set of AutoCAD lines using .NET”

  1. Hi Kean,

    Command "SHL" crash in my test in windows 7 home x64 + autocad 2011 x64.

    I try following code:

    Entity ent = (Entity)objs[1];
    cur.HandOverTo(ent, true, true);
    tr.AddNewlyCreatedDBObject(ent, true);
    //cur.Dispose();
    objs[0].Dispose();
    objs[2].Dispose();

    it works. Although I do find that it's up to the ObjectARX application to delete the old object in ObjectARX document.

  2. Kean Walmsley Avatar

    Hi Andy,

    Interesting, thanks.

    I'm not sure why this would be different on x64, to be honest, but it's good to know what worked for you.

    Regards,

    Kean

  3. Nothing to do with x64, it will crash on any platform, because the OpenCloseTransaction is trying to close an object that's already been closed by your code's call to cur.Dispose().

  4. Hi Kean - Great tool. I have found that when you get further away from 0,0 you get an exception

    An exception of type 'Autodesk.AutoCAD.Runtime.Exception' occurred in AcdbMgd.dll but was not handled in user code

    Additional information: eInvalidInput

    on the line

    DBObjectCollection objs = cur.GetSplitCurves(dc);

    1. Kean Walmsley Avatar
      Kean Walmsley

      Hi Brent,

      How far away are we talking? Geometric calculations at very large coordinates can be problematic due to the precision limits: even double precision struggles when you're working with small geometry at very large coordinates.

      We often transform such curves to be close to the origin before performing these operations and then transform the results back again.

      Regards,

      Kean

      1. factor of 1000 (m to mm)

        X = 6265041.3370 Y = 26108606.8939

        I think that is probably it then. I'll take a look at Transforming the curves.

        Cheers,

        Brent

  5. Hi Kean and readers

    There are two things I find confusing in the above doe:

    (1) the first is the Curve.StartParam property. the following is pasted from the autocad class reference guide:

    Curve.StartParam Property: Returns the parameter of the start of the curve. It returns a double. But what exactly does this mean?

    (2) The second: once a new "line" is created, doesn't it need to be added to the blocktable?

    rgds

    Ben

    1. Kean Walmsley Avatar

      Hi Ben,

      Here's a page on parameterization: en.wikipedia.org/wi...

      Although it's a bit complicated... curves are parameterized along their length. Lines go between 0 and 1, other curves may go between different integers. To get the mid-point of a line, get the point whose parameter is 0.5.

      The line gets swapped with one that's already in the database - no need to add it. A particularity of HandOverTo().

      Regards,

      Kean

      1. Hi Kean and readers,

        re: parameterization of curves/lines, as I understand it, if i have a line and i want to get to a point say 10 % along that line, then I would use a "parameter" of that line - in this case, the parameter would be something like 0.1? am i understanding correctly?

        thx and rgds
        Ben

        1. Hi Ben,

          That's correct. Although multi-segment objects won't follow the logic (as the parameter space may be broken up with segment 1 being 0..1, segment 2 being 1..2, etc. - and the segments being of different lengths).

          Regards,

          Kean

Leave a Reply

Your email address will not be published. Required fields are marked *