Controlling interactive polyline creation - Part 3

During the first two parts of this series we looked at different techniques for creating polylines programmatically in AutoCAD (while allowing the user to select the various vertices).

In this post look at the most advanced technique yet, the use of a "Jig". A Jig is a special construct in AutoCAD that hosts an object - in our case a polyline - and feeds user input information into this object, which then determines what graphics gets displayed. This is especially useful when dealing with complex objects, such as polylines with arc segments.

We're going to start with a fairly basic Jig, one that simply collects vertices and creates straight-line segments, but I'd quite like to take this further to support a number of different advanced capabilities: arc segments (which will require keyword implementation), dynamic dimensions, and possibly even undo (a request that just came in as a comment to the previous post).

Anyway, here's the C# code of the basic Jig implementation:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

namespace MyPlineApp

{

  public class MyPlineCmds

  {

    class PlineJig : EntityJig

    {

      // Maintain a list of vertices...

      // Not strictly necessary, as these will be stored in the

      // polyline, but will not adversely impact performance

      Point3dCollection m_pts;

      // Use a separate variable for the most recent point...

      // Again, not strictly necessary, but easier to reference

      Point3d m_tempPoint;

      Plane m_plane;

      public PlineJig(Matrix3d ucs)

        : base(new Polyline())

      {

        // Create a point collection to store our vertices

        m_pts = new Point3dCollection();

        // Create a temporary plane, to help with calcs

        Point3d origin = new Point3d(0, 0, 0);

        Vector3d normal = new Vector3d(0, 0, 1);

        normal = normal.TransformBy(ucs);

        m_plane = new Plane(origin, normal);

        // Create polyline, set defaults, add dummy vertex

        Polyline pline = Entity as Polyline;

        pline.SetDatabaseDefaults();

        pline.Normal = normal;

        pline.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0);

      }

      protected override SamplerStatus Sampler(JigPrompts prompts)

      {

        JigPromptPointOptions jigOpts =

          new JigPromptPointOptions();

        jigOpts.UserInputControls =

          (UserInputControls.Accept3dCoordinates |

          UserInputControls.NullResponseAccepted |

          UserInputControls.NoNegativeResponseAccepted

          );

        if (m_pts.Count == 0)

        {

          // For the first vertex, just ask for the point

          jigOpts.Message =

            "\nStart point of polyline: ";

        }

        else if (m_pts.Count > 0)

        {

          // For subsequent vertices, use a base point

          jigOpts.BasePoint = m_pts[m_pts.Count - 1];

          jigOpts.UseBasePoint = true;

          jigOpts.Message = "\nPolyline vertex: ";

        }

        else // should never happen

          return SamplerStatus.Cancel;

        // Get the point itself

        PromptPointResult res =

          prompts.AcquirePoint(jigOpts);

        // Check if it has changed or not

        // (reduces flicker)

        if (m_tempPoint == res.Value)

        {

          return SamplerStatus.NoChange;

        }

        else if (res.Status == PromptStatus.OK)

        {

          m_tempPoint = res.Value;

          return SamplerStatus.OK;

        }

        return SamplerStatus.Cancel;

      }

      protected override bool Update()

      {

        // Update the dummy vertex to be our

        // 3D point projected onto our plane

        Polyline pline = Entity as Polyline;

        pline.SetPointAt(

          pline.NumberOfVertices - 1,

          m_tempPoint.Convert2d(m_plane)

        );

        return true;

      }

      public Entity GetEntity()

      {

        return Entity;

      }

      public void AddLatestVertex()

      {

        // Add the latest selected point to

        // our internal list...

        // This point will already be in the

        // most recently added pline vertex

        m_pts.Add(m_tempPoint);

        Polyline pline = Entity as Polyline;

        // Create a new dummy vertex...

        // can have any initial value

        pline.AddVertexAt(

          pline.NumberOfVertices,

          new Point2d(0,0),

          0,0,0

        );

      }

      public void RemoveLastVertex()

      {

        // Let's remove our dummy vertex

        Polyline pline = Entity as Polyline;

        pline.RemoveVertexAt(m_pts.Count);

      }

    }

    [CommandMethod("MYPOLY")]

    public void MyPolyJig()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      // Get the current UCS, to pass to the Jig

      Matrix3d ucs =

        ed.CurrentUserCoordinateSystem;

      // Create our Jig object

      PlineJig jig = new PlineJig(ucs);

      // Loop to set the vertices directly on the polyline

      bool bSuccess = true, bComplete = false;

      do

      {

        PromptResult res = ed.Drag(jig);

        bSuccess =

          (res.Status == PromptStatus.OK);

        // A new point was added

        if (bSuccess)

          jig.AddLatestVertex();

        // Null input terminates the command

        bComplete =

          (res.Status == PromptStatus.None);

        if (bComplete)

          // Let's clean-up the polyline before adding it

          jig.RemoveLastVertex();

      } while (bSuccess && !bComplete);

      // If the jig completed successfully, add the polyline

      if (bComplete)

      {

        // Append entity

        Database db = doc.Database;

        Transaction tr =

          db.TransactionManager.StartTransaction();

        using (tr)

        {

          BlockTable bt =

            (BlockTable)tr.GetObject(

              db.BlockTableId,

              OpenMode.ForRead,

              false

            );

          BlockTableRecord btr =

            (BlockTableRecord)tr.GetObject(

              bt[BlockTableRecord.ModelSpace],

              OpenMode.ForWrite,

              false

            );

          btr.AppendEntity(jig.GetEntity());

          tr.AddNewlyCreatedDBObject(jig.GetEntity(), true);

          tr.Commit();

        }

      }

    }

  }

}

That's it for the basic Jig implementation. For further information on Jigs, I suggest taking a look at the "EllipseJig" sample on the ObjectARX SDK (under samples/dotNet) and the ObjectARX Developer's Guide (the C++ documentation is much more extensive for now). I'll also take a closer look in future posts, so please keep your questions coming.

Update

For an updated implementation that supports keywords, arc segments and has better support for non-standard UCS settings, please see this post.

26 responses to “Controlling interactive polyline creation - Part 3”

  1. Thank you, Kean:
    I will try your code.
    Maybe I am greed, when you are not busy, could you have a sample code for using jig as ghost object, a object will disappear once REDRAW. also, can I use Hatch as jig object, and as ghost object.
    Once again, thank you very much and have a great thanksgiving.

    Wes

  2. Hi Wes,

    If you don't append the entity the modelspace, that should meet your requirement. Just to try it out, change the last "if (bComplete)" line to "if (bComplete && false)", which should stop that whole section from being executed.

    BTW - you should look at the following post for how to implement UNDO.

    Cheers,

    Kean

  3. Hi Kean,
    I have a lot of lines, curves and 2d-polylines in a Civil 3D drawing which I have copied down to a Z of 0.0, these form various things such as kerb lines etc. I need to pick the first element and then convert that to a polyline and automatically add all subsequent elements that on the end of that line, so that I finish up with a 2D-polyline.
    How can I achieve this in C#

    Ben

  4. Kean Walmsley Avatar

    Hi Ben,

    I'm not familiar with the specific functionality in the Civil 3D API, but I do know it's a COM API. So you'll need to use COM interop from .NET to call the Civil 3D API on the elements in question.

    Regards,

    Kean

  5. Kélcyo Pereira Avatar
    Kélcyo Pereira

    Hi Kean,
    Very good this post.
    Interesting would be an sample to create one polyline from lines and curves existing, that coincide the link points or a tolerance of distance.
    Grateful.

    Kelcyo

  6. 1- Which is respect between creating a polyline or line by creating a data base and a transaction (your code) and creating a polyline by using the acad functions .

    example:

    using System;
    using Autodesk.AutoCAD.Runtime;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using Autodesk.AutoCAD.Interop;
    using Autodesk.AutoCAD.Interop.Common;
    using Autodesk.AutoCAD.Colors;
    using Autodesk.AutoCAD.ApplicationServices;
    using Autodesk.AutoCAD.DatabaseServices;
    using Autodesk.AutoCAD.Geometry;
    using Autodesk.AutoCAD.EditorInput;

    [assembly: CommandClass(typeof(ClassLibrary.civiClass))]

    namespace ClassLibrary
    {
    /// <summary>
    /// Summary description for civiClass.
    /// </summary>
    public class civiClass
    {

    //public AcadApplication app = GetActiveObject("AutoCAD.Application");
    public civiClass()
    {

    }

    // Define Command "AsdkCmd1"
    [CommandMethod("line")]
    public void line() // This method can have any name
    {

    AcadApplication app = new AcadApplication();

    AcadDocument doc1 = app.ActiveDocument;

    //This example adds a line in model space

    AcadLine lineObj;

    double[] startPoint = new double[3];

    double[] endPoint = new double[3];

    //Define the start and end points for the line

    startPoint[0] = 1; startPoint[1] = 1; startPoint[2] = 0;

    endPoint[0] = 50; endPoint[1] = 200; endPoint[2] = 0;

    //Create the line in model space

    lineObj = doc1.ModelSpace.AddLine(startPoint, endPoint);

    app.Application.Visible = true;

    doc1.Regen(AcRegenType.acAllViewports);

    app.ZoomAll();

    }
    }
    }

  7. How to make to test if the autocad process is already executed
    And how to recover the AcadApplication without create a new application.

    thank you

  8. what the difference between creating a polyline or line by creating a data base and a transaction (your code) and creating a polyline by using the Acad functions

  9. what is the difference between creating a polyline or line by creating a data base and a transaction (your code) and creating a polyline by using the Acad functions

    using System;
    using Autodesk.AutoCAD.Runtime;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using Autodesk.AutoCAD.Interop;
    using Autodesk.AutoCAD.Interop.Common;
    using Autodesk.AutoCAD.Colors;
    using Autodesk.AutoCAD.ApplicationServices;
    using Autodesk.AutoCAD.DatabaseServices;
    using Autodesk.AutoCAD.Geometry;
    using Autodesk.AutoCAD.EditorInput;

    [assembly: CommandClass(typeof(ClassLibrary.createline))]

    namespace ClassLibrary
    {
    ///
    ///
    ///
    public class createline
    {

    // Define Command "AsdkCmd1"
    [CommandMethod("line")]
    public void line() // This method can have any name
    {

    AcadApplication app = new AcadApplication();

    AcadDocument doc1 = app.ActiveDocument;

    //This example adds a line in model space

    AcadLine lineObj;

    double[] startPoint = new double[3];

    double[] endPoint = new double[3];

    //Define the start and end points for the line

    startPoint[0] = 1; startPoint[1] = 1; startPoint[2] = 0;

    endPoint[0] = 50; endPoint[1] = 200; endPoint[2] = 0;

    //Create the line in model space

    lineObj = doc1.ModelSpace.AddLine(startPoint, endPoint);

    app.Application.Visible = true;

    doc1.Regen(AcRegenType.acAllViewports);

    app.ZoomAll();

    }
    }
    }

  10. Asmaa -

    1) You're using the COM API (which has some overhead, as each "operation" has a fair amount of object marshalling to do) rather than the .NET API (which is much closer to ObjectARX).

    2) It's a pretty standard technique to use GetObject() or GetActiveObject() and then CreateObject() if an error is returned (i.e. AutoCAD is not running). This is using the COM API, of course - which isn't really a focus on this blog (I tend to focus on .NET).

    Kean

  11. hi kean,

    Thank you very much you are formidable

  12. hi kean,

    Je n’arrive pas à comprendre quel est la différence entre COM API et .NET API ?
    et quand je dois utiliser COM API ou .NET APPI

  13. La difference est expliquée dans cet article précédent.

    Kean

  14. Thank you very much
    j'ai bien compris

  15. Hi Kean,

    Is it possible that using this code from a button on a palette changes the behaviour of the jig?

    When running similar code as yours from a command as entry-point, everything works as expected.
    When running from a button on a palette, object snapping does odd things and the line, presented by the jig, is no longer visible.

    Any thoughts?

  16. Hi Bert,

    Are you remembering to lock the document? That's the classic issue in this scenario.

    Commands generally lock the current document automatically (with the notable exception of "session" commands), but modeless dialogs such as palettes do not.

    Regards,

    Kean

  17. Hi,

    I have the same problem. I lock the document, but when running from a dialog or palette the polyline is almost no visible. It's like a very fast flickering.

    Thanks,

    Francisco

  18. I suggest posting your code to the ADN team or the AutoCAD .NET Discussion Group. The recommended way for a modeless UI to interact with AutoCAD (and this is the technique we tend to use internally) is to fire commands - anything else does run the risk of strange behaviour.

    Kean

  19. Can I impose upon you to rewrite the this jig example using VB.net rather than C#. I'm a convert who jumped from Autolisp to VB.net in response to the boss. I'm still having trouble translating C# code to the equivalent in VB.net.

    Thanks.

  20. Please see this post for some tools that will help convert C# to VB.NET.

    Kean

  21. would it be to much trouble to enable OSNAP on the jig while the object is being created (attm, you can osnap on other objects in the model while the jig is still active, but not on jig it self).

    1. Kean Walmsley Avatar

      That's tricky... you could either implement your own osnap mechanism that works with the objects in your jig (lots of work), or you might try creating database resident - but temporary - objects as they're being jigged, as these should get snapped to.

      Good luck!

      Kean

      1. thank you for the swift reply! I guess I'll go with the "temporary object" method.

  22. thx Mr Kean if only the developer guide had this sort of tutorials......

  23. Thank you Kean:
    I used your code in AUTOCAD 2014, but in AUTOCAD 2016
    unable zoom while polyline creation. "Point or option keyword required."
    Thank you very much in advance.
    Michael

  24. Rodrigue Kamdieun Avatar
    Rodrigue Kamdieun

    Thank you Sir.
    This code saved me big time.

Leave a Reply to Ben Senior Cancel reply

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