Advanced jigging with AutoCAD .NET - adding keywords

This post extends the polyline-creation jig shown in the previous entry to support the use of keywords both for arc segments and for undo.

A few notes:

  • I removed the use of a separate vertex list, as it proved to be less necessary than needed
  • This implementation supports Undo, and the toggling between Line segment and Arc segment entry
  • Arc segments have a fixed bulge of 1.0, which is actually quite useful if drawing a cloud, but not really useful for much else. Generally the bulge should be adjusted according to the position of the cursor relative to the previous point, which may be something I attempt in a future post
  • I've also streamlined some of the other parts of code (such as using the basepoint in the jig - which we don't actually need, as we allow the polyline to draw itself)

The code has become quite a bit more complex, and could probably do with some additional performance tuning, but it should allow you to get the idea of what can be done (and one way of approaching it). I should also say that - and this holds true for any of the code samples posted in this blog - it should be thoroughly tested before being integrated into your own application. Hopefully that goes without saying...

Here's the C# code:

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

    {

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

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

      Point3d m_tempPoint;

      Plane m_plane;

      bool m_isArcSeg = false;

      bool m_isUndoing = false;

      // At this stage, weour arc segments will

      // have a fixed bulge of 1.0...

      // Later we may update the routine to determine

      // the bulge based on the relative location

      // of the cursor

      const double kBulge = 1.0;

      public PlineJig(Matrix3d ucs)

        : base(new Polyline())

      {

        // 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;

        AddDummyVertex();

      }

      protected override SamplerStatus Sampler(JigPrompts prompts)

      {

        JigPromptPointOptions jigOpts =

          new JigPromptPointOptions();

        jigOpts.UserInputControls =

          (UserInputControls.Accept3dCoordinates |

          UserInputControls.NullResponseAccepted |

          UserInputControls.NoNegativeResponseAccepted

          );

        m_isUndoing = false;

        Polyline pline = Entity as Polyline;

        if (pline.NumberOfVertices == 1)

        {

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

          jigOpts.Message =

            "\nStart point of polyline: ";

        }

        else if (pline.NumberOfVertices > 1)

        {

          // For subsequent vertices, use a base point

          if (m_isArcSeg)

          {

            jigOpts.SetMessageAndKeywords(

              "\nSpecify endpoint of arc or [Line/Undo]: ",

              "Line Undo"

            );

          }

          else

          {

            jigOpts.SetMessageAndKeywords(

              "\nSpecify next point or [Arc/Undo]: ",

              "Arc Undo"

            );

          }

        }

        else // should never happen

          return SamplerStatus.Cancel;

        // Get the point itself

        PromptPointResult res =

          prompts.AcquirePoint(jigOpts);

        if (res.Status == PromptStatus.Keyword)

        {

          if (res.StringResult == "Arc")

          {

            m_isArcSeg = true;

          }

          else if (res.StringResult == "Line")

          {

            m_isArcSeg = false;

          }

          else if (res.StringResult == "Undo")

          {

            m_isUndoing = true;

          }

          return SamplerStatus.OK;

        }

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

        {

          // Check if it has changed or not

          // (reduces flicker)

          if (m_tempPoint == res.Value)

          {

            return SamplerStatus.NoChange;

          }

          else

          {

            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)

        );

        // If it's supposed to be an arc segment,

        // set the bulge appropriately

        if (m_isArcSeg)

        {

          pline.SetBulgeAt(

            pline.NumberOfVertices-1,

            kBulge

          );

        }

        // Otherwise, it's a line, so set the bulge

        // to zero

        else

        {

          pline.SetBulgeAt(

            pline.NumberOfVertices-1,

            0

          );

        }

        return true;

      }

      public Entity GetEntity()

      {

        return Entity;

      }

      public bool IsArcSegment()

      {

        return m_isArcSeg;

      }

      public bool IsUndoing()

      {

        return m_isUndoing;

      }

      public void AddDummyVertex()

      {

        // Create a new dummy vertex...

        // can have any initial value

        Polyline pline = Entity as Polyline;

        pline.AddVertexAt(

          pline.NumberOfVertices,

          new Point2d(0,0),

          0,0,0

        );

      }

      public void RemoveDummyVertex()

      {

        Polyline pline = Entity as Polyline;

        // Let's first remove our dummy vertex       

        if (pline.NumberOfVertices > 0)

        {

          pline.RemoveVertexAt(pline.NumberOfVertices - 1);

        }

        // And then check the type of the last segment

        if (pline.NumberOfVertices >= 2)

        {

          double blg =

            pline.GetBulgeAt(pline.NumberOfVertices - 2);

          m_isArcSeg = (blg != 0);

        }

      }

      public void AdjustSegmentType(bool isArc)

      {

        // Change the bulge of the previous segment,

        // if necessary

        double bulge = 0.0;

        if (isArc)

          bulge = kBulge;

        Polyline pline = Entity as Polyline;

        if (pline.NumberOfVertices >= 2)

          pline.SetBulgeAt(pline.NumberOfVertices - 2, bulge);

      }

    }

    [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 bPoint = false;

      bool bKeyword = false;

      bool bComplete = false;

      do

      {

        PromptResult res = ed.Drag(jig);

        bPoint =

          (res.Status == PromptStatus.OK);

        // A new point was added

        if (bPoint)

          jig.AddDummyVertex();

        bKeyword =

          (res.Status == PromptStatus.Keyword);

        if (bKeyword)

        {

          if (jig.IsUndoing())

          {

            jig.RemoveDummyVertex();

          }

          else

          {

            jig.AdjustSegmentType(jig.IsArcSegment());

          }

        }

        // Null input terminates the command

        bComplete =

          (res.Status == PromptStatus.None);

        if (bComplete)

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

          jig.RemoveDummyVertex();

      } while ((bPoint || bKeyword) && !bComplete);

      // If the jig completed successfully,

      // add the polyline

      if (bComplete)

      {

        Polyline pline = jig.GetEntity() as Polyline;

        if (pline.NumberOfVertices > 1)

        {

          // 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(pline);

            tr.AddNewlyCreatedDBObject(pline, true);

            tr.Commit();

          }

        }

      }

    }

  }

}

Update

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

  1. Your code snippet works for me, but I am having trouble converting this to C++. I admit to not being all that familiar with C#, but I'm hoping you can help me. Where are the variables PromptStatus and Entity defined?

    It seems like the ObjectARX 2007 C++ interface for AcEdJig is half baked compared to the c# interface. Unfortunate, because I can't use anything but C++ for my project.

    Right now I'm frustrated because it seems like AcquirePoint returns nothing but kNormal or kKW1 through kKW9. I never get kNull, kCancel or anything else. Neither does there seem to be an equivalent to PromptStatus. Here is my set user controls function call:
    setUserInputControls((UserInputControls)
    (AcEdJig::kAccept3dCoordinates
    | AcEdJig::kNoNegativeResponseAccepted
    | AcEdJig::kNullResponseAccepted
    | AcEdJig::kInitialBlankTerminatesInput
    | AcEdJig::kAnyBlankTerminatesInput ));

    I appreciate any answers to the above questions as well as general help with the acquirePoint function in ObjectArx 2007.

  2. Kean Walmsley Avatar

    There is no direct equivalent for PromptStatus in ObjectARX (C++) - this was created to wrap a number of concepts (the ErrorStatus and the value returned, for instance). Enity is AcDbEntity in the C++ API.

    I'd suggest looking at the EllipseJig C++ sample (under samples\database\elipsjig_dg in the ObjectARX SDK).

    Otherwise, please post your questions to the ObjectARX Discussion Group or via the ADN website, if you're a member.

    Regards,

    Kean

  3. Massimo Cicognani Avatar

    I'd revive this old post for a simple question:

    While jigging, the segment already added to the Polyline does not count for dynamic input, like osnaps or object tracking.
    How can we make the PLineJig act more like a normal Pline?
    I tried to add the entity to the Database since the beginning of the jigging, but it doesn't work, or maybe I'm not able to update the entity correctly inside the transaction.
    I guess I'd have to get rid of the transaction management...

  4. Yes - making it database-resident would help, I expect. You may need to create a separate database-resident polyline that you update as you acquire values (and just jig the latest segment, for example).

    I haven't tried to do this, myself: I'd suggest contacting the ADN team, if you're a member.

    Kean

  5. bonjour,
    en VBA la commande :AngleFromXAxis:

    exp:
    dim angl_segment as double

    angl_segment = ThisDrawing.Utility.AngleFromXAxis(pt0, pt1)

    permet de trouver l'angle entre le segment (pt0, pt1) et l'axe des X
    est ce que il existe une commande équivalente en c#?

  6. est ce qu'il y a une fonction en .net api équivalente a cette fonction en com api(acad) : Move(object FromPoint, object ToPoint);

  7. Asmaa -

    This is not a mechanism for getting support. If your question is not directly related to a post, please post to the AutoCAD .NET Discussion Group or to the ADN website, if you're a member.

    Il existe aussi le forum francais pour AutoCAD, si jamais.

    Regards,

    Kean

  8. Howdy Kean,

    I know this is long after the original posting, but you keep these up here so us newbies can always learn from them, right?

    Do you take requests?

    I am having a similar problem to that in the first comment from Matt, except that you have gotten me going trying to learn F#. I cannot for the life of me figure out how to code a jig in F#, or even how to handle an "Entity", which has no available constructor. Converting this code to F# is as far beyond my abilities at this point as is the peak of Mount Everest.

    Could you see your way clear to posting a sample of a jig in F#?

    Thank you, Kean, and thanks for all the great stuff you've given us already.

    -- Geoff

  9. Hi Geoff,

    I do take requests - I'll add this to my list.

    As I'm taking a special (personal) interest in F#, these days, feel free to email me your code, and I'll take a look.

    Kean

  10. Is there a limit of 3 on the amount of Keywords allowed? I get some really strange behavior when I go above 3 keywords.

  11. No - there's no fixed limit. You may be suffering from clashing mnemonics (if your keywords start with the same letter(s)), but otherwise there shouldn't be a limit (at least not one that low).

    Kean

  12. I've successfully implemented a PLine jig using the above. But the routine has 2 problems:

    1. It doesn't respond to the Ortho and Polar toggles. Points can be input at any angle.

    2. You have to end the command with <esc> rather than the standard <enter> or <spacebar>. This causes confusion for the user because <esc> is normally a cancel, not an end command.

    Each point is acquired using:

    myPR = prompts.AcquirePoint("Select a point")

    I can't find any reference to adding options to this.

    Do you have any ideas?

    TIA

  13. Kean Walmsley Avatar

    I unfortunately don't have time to look into this.

    Please submit your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Kean

  14. I have done so with no results so far. I was hoping you could shed some insight. Or maybe it just isn't supported.

  15. I appreciate the reply Kean. I continue to struggle with this issue. I have rules out mnemonics issues. If you have time perhaps you can try adding a 4th keyword to your code above and see if it works for you. If not I fully understand.

    Here is the Code I am going off of, note some keywords are commented out just as I test around this issue. The strangest part is that whatever keywords I set as the first 3, always work just fine, anything in positions 4 and above throw the error. I can literally move them around position wise while debugging to utilize the functions I need at the moment.

    JigPromptPointOptions jigOpts = new JigPromptPointOptions();
    jigOpts.UserInputControls = (UserInputControls.Accept3dCoordinates | UserInputControls.NoZDirectionOrtho | UserInputControls.NoNegativeResponseAccepted | UserInputControls.NullResponseAccepted);
    //jigOpts.Keywords.Add(!LockPos ? "Lock" : "unLock");
    //jigOpts.Keywords.Add("Prev");
    jigOpts.Keywords.Add("Next");
    jigOpts.Keywords.Add(_promptCounter == 0 ? "Rotate" : "Move");
    //jigOpts.Keywords.Add("Color");
    jigOpts.Keywords.Add(!TextOff ? "Text off" : "Text on");
    jigOpts.Keywords.Add("Save");
    jigOpts.AppendKeywordsToMessage = true;
    jigOpts.Message = "\nAlign Points as best you can, Click to Lock/Unlock Points:";
    PromptPointResult ptRes = prompts.AcquirePoint(jigOpts);

  16. FYI posting to ADN also so definitely ignore if it doesn't pique your curiosity :).

  17. I'm certainly curious - I just don't have the time. 🙁 🙂

    I had a quick look in our call tracker, and see that you've managed to resolve the problem yourself, for now.

    Kean

  18. Hello, I would like to know if we can use "kDisableDirectDistanceInput" on JigPromptPointOptions with AutoCad 2019 API? And if isn't available, how can i do without ?
    Thanks

    1. Please post your support questions to the AutoCAD .NET forum.

      Thank you,

      Kean

Leave a Reply to S.Hamel Cancel reply

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