Jigging an AutoCAD block with attributes using .NET

Thanks, once again, to Philippe Leefsma, a DevTech engineer based in Prague, for contributing the code for this post. While researching an issue he was working on Philippe stumbled across a comment on this previous post where I more-or-less said jigging attributes wasn't possible. Ahem. Anyway, Philippe decided to – quite rightly – prove me wrong, and the result is today's post. 🙂

It turns out that the trick to jigging a block with attributes is to add the block reference to the database prior to running the jig. I'd been coming at this from another direction – working out how to call through to the right version of the ObjectARX function, the one that allows the block reference to be in-memory rather than db-resident – but Philippe's approach means that's no longer needed. I see this technique as potentially being useful when jigging other entities that benefit from being database resident (Solid3d objects spring to mind), so I really appreciate Philippe's hard work on this.

Here's the C# code which I've edited for posting:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using System.Collections.Generic;

 

namespace BlockJig

{

  class CBlockJig : EntityJig

  {

    private Point3d _pos;

    private Dictionary<string, Point3d> _attPos;

    private Transaction _tr;

 

    public CBlockJig(Transaction tr, BlockReference br)

      : base(br)

    {

      _pos = br.Position;

 

      // Initialize our dictionary with the tag /

      // AttributeDefinition position

 

      _attPos = new Dictionary<string, Point3d>();

 

      _tr = tr;

 

      BlockTableRecord btr =

        (BlockTableRecord)_tr.GetObject(

          br.BlockTableRecord,

          OpenMode.ForRead

        );

 

      if (btr.HasAttributeDefinitions)

      {

        foreach (ObjectId id in btr)

        {

          DBObject obj =

            tr.GetObject(id, OpenMode.ForRead);

          AttributeDefinition ad =

            obj as AttributeDefinition;

 

          if (ad != null)

          {

            _attPos.Add(ad.Tag, ad.Position);

          }

        }

      }

    }

 

    protected override bool Update()

    {

      BlockReference br = Entity as BlockReference;

 

      br.Position = _pos;

 

      if (br.AttributeCollection.Count != 0)

      {

        foreach (ObjectId id in br.AttributeCollection)

        {

          DBObject obj =

            _tr.GetObject(id, OpenMode.ForRead);

          AttributeReference ar =

            obj as AttributeReference;

 

          // Apply block transform to att def position

 

          if (ar != null)

          {

            ar.UpgradeOpen();

            ar.Position =

              _attPos[ar.Tag].TransformBy(br.BlockTransform);

          }

        }

      }

 
0;   
return true;

    }

 

    protected override SamplerStatus Sampler(JigPrompts prompts)

    {

      JigPromptPointOptions opts =

        new JigPromptPointOptions("\nSelect insertion point:");

      opts.BasePoint = new Point3d(0, 0, 0);

      opts.UserInputControls =

        UserInputControls.NoZeroResponseAccepted;

 

      PromptPointResult ppr = prompts.AcquirePoint(opts);

 

      if (_pos == ppr.Value)

      {

        return SamplerStatus.NoChange;

      }

 

      _pos = ppr.Value;

 

      return SamplerStatus.OK;

    }

 

    public PromptStatus Run()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      PromptResult promptResult = ed.Drag(this);

      return promptResult.Status;

    }

  }

 

  public class Commands

  {

    [CommandMethod("BJ")]

    static public void BlockJig()

    {

      Document doc =

      
0;
Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      PromptStringOptions pso =

        new PromptStringOptions("\nEnter block name: ");

      PromptResult pr = ed.GetString(pso);

 

      if (pr.Status != PromptStatus.OK)

        return;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord space =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForRead

          );

 

        if (!bt.Has(pr.StringResult))

        {

          ed.WriteMessage(

            "\nBlock \"" + pr.StringResult + "\" not found.");

          return;

        }

 

        space.UpgradeOpen();

 

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[pr.StringResult],

            OpenMode.ForRead);

 

    &#
0160;  
// Block needs to be inserted to current space before

        // being able to append attribute to it

 

        BlockReference br =

          new BlockReference(new Point3d(), btr.ObjectId);

        space.AppendEntity(br);

        tr.AddNewlyCreatedDBObject(br, true);

 

        if (btr.HasAttributeDefinitions)

        {

          foreach (ObjectId id in btr)

          {

            DBObject obj =

              tr.GetObject(id, OpenMode.ForRead);

            AttributeDefinition ad =

              obj as AttributeDefinition;

 

            if (ad != null && !ad.Constant)

            {

              AttributeReference ar =

                new AttributeReference();

              ar.SetAttributeFromBlock(ad, br.BlockTransform);

              ar.Position =

                ad.Position.TransformBy(br.BlockTransform);

 

              ar.TextString = ad.TextString;

 

              br.AttributeCollection.AppendAttribute(ar);

              tr.AddNewlyCreatedDBObject(ar, true);

            }

          }

        }

 

        // Run the jig

 

        CBlockJig myJig = new CBlockJig(tr, br);

 

        if (myJig.Run() != PromptStatus.OK)

          return;

 

        // Commit changes if user accepted, otherwise discard

 

        tr.Commit();

      }

    }

  }

}

When you run the BJ command (short for BlockJig) and specify the name of a block in the current drawing which contains attributes, you'll now see the attributes with their default values shown as part of the block being jigged. Implementing the code to allow editing of those attributes after insertion is left as an exercise for the reader.

Update:

This code didn't work for a few situations, such as when using justification (attributes would end up at the origin after being dragged) or with MText attributes (which would start at the origin until the mouse was moved).

A big thanks to Roland Feletic from PAUSER ZT-GMBH for helping identify and diagnose the various cases.

Here's the updated C# code:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using System.Collections.Generic;

 

namespace BlockJigApplication

{

  class AttInfo

  {

    private Point3d _pos;

    private Point3d _aln;

    private bool _aligned;

 

    public AttInfo(Point3d pos, Point3d aln, bool aligned)

    {

      _pos = pos;

      _aln = aln;

      _aligned = aligned;

    }

 

    public Point3d Position

    {

      set { _pos = value; }

      get { return _pos; }

    }

 

    public Point3d Alignment

    {

      set { _aln = value; }

      get { return _aln; }

    }

 

    public bool IsAligned

    {

      set { _aligned = value; }

      get { return _aligned; }

    }

  }

 

  class BlockJig : EntityJig

  {

    private Point3d _pos;

    private Dictionary<ObjectId, AttInfo> _attInfo;

    private Transaction _tr;

 

    public BlockJig(

      Transaction tr,

      BlockReference br,

      Dictionary<ObjectId, AttInfo> attInfo

    ) : base(br)

    {

      _pos = br.Position;

      _attInfo = attInfo;

      _tr = tr;

    }

 

    protected override bool Update()

    {

      BlockReference br = Entity as BlockReference;

 

      br.Position = _pos;

 

      if (br.AttributeCollection.Count != 0)

      {

        foreach (ObjectId id in br.AttributeCollection)

        {

          DBObject obj =

            _tr.GetObject(id, OpenMode.ForRead);

          AttributeReference ar =

            obj as AttributeReference;

 

          // Apply block transform to att def position

 

          if (ar != null)

          {

            ar.UpgradeOpen();

            AttInfo ai = _attInfo[ar.ObjectId];

            ar.Position =

              ai.Position.TransformBy(br.BlockTransform);

            if (ai.IsAligned)

            {

              ar.AlignmentPoint =

                ai.Alignment.TransformBy(br.BlockTransform);

            }

            if (ar.IsMTextAttribute)

            {

              ar.UpdateMTextAttribute();

            }

          }

        }

      }

      return true;

    }

 

    protected override SamplerStatus Sampler(JigPrompts prompts)

    {

      JigPromptPointOptions opts =

        new JigPromptPointOptions("\nSelect insertion point:");

      opts.BasePoint = new Point3d(0, 0, 0);

      opts.UserInputControls =

        UserInputControls.NoZeroResponseAccepted;

 

      PromptPointResult ppr = prompts.AcquirePoint(opts);

 

      if (_pos == ppr.Value)

      {

        return SamplerStatus.NoChange;

      }

 

      _pos = ppr.Value;

 

      return SamplerStatus.OK;

    }

 

    public PromptStatus Run()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      PromptResult promptResult = ed.Drag(this);

      return promptResult.Status;

    }

  }

 

  public class Commands

  {

    [CommandMethod("BJ")]

    static public void BlockJigCmd()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      PromptStringOptions pso =

        new PromptStringOptions("\nEnter block name: ");

      PromptResult pr = ed.GetString(pso);

 

      if (pr.Status != PromptStatus.OK)

        return;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

        �
160; (
BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

 

        if (!bt.Has(pr.StringResult))

        {

          ed.WriteMessage(

            "\nBlock \"" + pr.StringResult + "\" not found.");

          return;

        }

 

        BlockTableRecord space =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForWrite

          );

 

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[pr.StringResult],

            OpenMode.ForRead);

 

        // Block needs to be inserted to current space before

        // being able to append attribute to it

 

        BlockReference br =

          new BlockReference(new Point3d(), btr.ObjectId);

        space.AppendEntity(br);

        tr.AddNewlyCreatedDBObject(br, true);

 

        Dictionary<ObjectId, AttInfo> attInfo =

          new Dictionary<ObjectId,AttInfo>();

 

        if (btr.HasAttributeDefinitions)

        {

          foreach (ObjectId id in
btr)

          {

            DBObject obj =

              tr.GetObject(id, OpenMode.ForRead);

            AttributeDefinition ad =

              obj as AttributeDefinition;

 

            if (ad != null && !ad.Constant)

            {

              AttributeReference ar =

                new AttributeReference();

 

              ar.SetAttributeFromBlock(ad, br.BlockTransform);

              ar.Position =

                ad.Position.TransformBy(br.BlockTransform);

 

              if (ad.Justify != AttachmentPoint.BaseLeft)

              {

                ar.AlignmentPoint =

                  ad.AlignmentPoint.TransformBy(br.BlockTransform);

              }

              if (ar.IsMTextAttribute)

              {

                ar.UpdateMTextAttribute();

              }

 

              ar.TextString = ad.TextString;

 

              ObjectId arId =

                br.AttributeCollection.AppendAttribute(ar);

              tr.AddNewlyCreatedDBObject(ar, true);

 

              // Initialize our dictionary with the ObjectId of

              // the attribute reference + attribute definition info

 

              attInfo.Add(

                arId,

                new AttInfo(

                  ad.Position,

                  ad.AlignmentPoint,

                  ad.Justify != AttachmentPoint.BaseLeft

                )

              );

            }

          }

        }

        // Run the jig

 

        BlockJig myJig = new BlockJig(tr, br, attInfo);

 

        if (myJig.Run() != PromptStatus.OK)

          return;

 

        // Commit changes if user accepted, otherwise discard

 

        tr.Commit();

      }

    }

  }

}

A few comments on this code:

  • It's been refactored to make a single pass through the block definition to both create the block reference and collect the attribute information to store in our dictionary.
  • The attribute information is now held in a class, which allows us to store more than just the position in our dictionary (without using multiple dictionaries), I went to the effort of exposing public properties for the various private members, as this is generally a good technique to use (if a little redundant, here).
  • The dictionary now stores this attribute information against an ObjectId rather than the tag string. Roland made the excellent point that blocks can contain attributes with duplicate tags, so this is much safe. We also had to use the ObjectId of the AttributeReference, as later on inside the jig's Update() function we no longer have access to the AttributeDefinition.

Update 2:

Please see this more recent post for an implementation that works properly with multiline attributes, UCS and annotation scaling.

37 responses to “Jigging an AutoCAD block with attributes using .NET”

  1. Thanks Kean, I wish you'd posted this code a while back; it would have come in handy for the application I'm writing (and by writing I mean converting from AC2006 VBA to AC2009 C# .NET).

  2. That did the trick for doing a cylinder jig...thanks!

  3. Hallo Kean
    thank you for these great posts. it helped me a lot. But I have one question left. Maybe you could point me into the right direction.

    I noticed a difference between inserting a dynamic block through the jig and through the "SendStringToExecute"-Method. While inserting a dynamic block through the "SendStringToExectute" -method, when i have to chose the insertion point, with having the block-preview on the cursor, I am able to press the control-key to cycle through all the grip points of the block, also the block itself aligns to lines etc. And at last I can end the insertion command with a mouse-right-click.

    So the question is: Is there a possibility to adopt this insertion behavior for the jig insertion?

    Thanks.
    Thomas

  4. Kean Walmsley Avatar
    Kean Walmsley

    Hi Thomas,

    I believe this is all possible for you to implement using a standard Jig, but to get parity with the standard INSERT capabilities it may prove to be a lot of work (and if that's what you want calling INSERT is probably the simplest approach).

    A Jig is good if you want to streamline or simplify the insertion process.

    Regards,

    Kean

  5. Thx Kean,
    I think the most important advantage of jigs is that the application is always synchronized. When i call the insert command, i have to deal with events like "CommandEnded" which makes the code really ugly.
    Anyway if find a little time, i'll try ti improve the jigs i already have.

  6. Hi Kean,

    After some time without commentting on you blog, I'm having a doubt. I was trying to use multiple entities Jig, base on the code of the link :
    cupocadnet.blogspot.com/2009/03/jigging-multiple-entities-with-drawjig.html

    The problem I'm facing is: Snap don't work, and when using zoom in/out (with the mouse roller/scroll), the basepoint loses precision.

    Is there eny limitation on DrawJig Method.
    I was thinking about creating a temporary Group to group the entities and after that start an EntityJig only with the createds group, but I'm not sure if it is the right track to take...

    Thanks for any help and/or idea...

    Best Regards,
    Felipe
    SP/Brasil

  7. Hi Felipe,

    I'm not sure what you mean about snaps: are you trying to snap to existing geometry (which should work) or somehow snap to the geometry being drawn by the jig (which shouldn't, for a DrawJig, at least)?

    Also, I'm afraid I barely have time to help resolve issues with code I've posted - there's almost no chance of me finding time to help with issues related to other people's posts.

    Regards,

    Kean

  8. Kean,

    The problem with the snaps occurs with an existing geometry... The snap symbols appears, but at the mouse-button click, the acquired point is not the pretended snap, but the "mouse over" coordinate.

    I didn't meant you to support other guy's blog.. sorry about that..

    I'll keep trying, and if I get the solution, I could post it back here, if you don't mind...

    Regards,
    Felipe

  9. Hi Felipe,

    Please do - I wish I had time to help you with this, but unfortunately it's a very busy time for me.

    Regards,

    Kean

  10. Kean,

    I've found a solution.
    - The loose of precision occurs (probably) because of the various Entity.TransformBy(Matrix.Displacement)... I think that is bacause of a numeric imprecision, or maybe, some kind of delay/lag on the operations.
    - The "snap" didn't work because after the (PromptResult == OK) there was no "Entity.TransformBy(Matrix)" to the last point, because this transformation occurs in the WorldDraw method.

    My approach to solve both problems was clonning each entity in the WorldDraw method in each pass, and apply the transformation from a basePoint to the actualPoint.
    In the Drag function, on the (PromptResult==OK), I did the same, but with the original Entities from the basePoint to the last or selected point.

    It works now... maybe there is some better solution, but for now, I'm happy... =)

    Regards,
    Felipe
    SP/BRA

  11. Felipe,

    I'm glad you have a working solution.

    Regards,

    Kean

  12. Thanks Kean, this was a tremendous help. Is there a particular reason to manually change the positions every update instead of using .TransformBy() on just the blockref?

    It seems like a good way to trim a lot of the code, no need for Attinfo class or changing position of attributes. If it is faster or not I do not know, but easier for me to understand. I just get a vector from current position to new Sampler position.

  13. Kean Walmsley Avatar

    Hi Nick,

    I inherited this code and it's been some time since I worked on it.

    I suspect there was no good reason: if it works for you, then great! 🙂

    Regards,

    Kean

  14. Ognyan Dimitrov Avatar

    Can we load a dynamic block into jig and set it graphical properties in this "interactive" manner?

  15. Possibly, although I haven't done so, myself. Have you tried it?

    Kean

  16. Ognyan Dimitrov Avatar
    Ognyan Dimitrov

    Nope. I did not yet, but I am going to do it in the next few days. I am sure it will work, because there are some big dynamic objects from syscad that are stretchable/editable in the same visualized way - jig-like - and have lots of dynamic dimensions that change through the whole object when you stretch just one point of it while refreshing the graphical representation constantly. My goal is to fully understand the jig and dynamic dimensions and then do it. I will paste all the code here when I am ready. Your blog, links, videos, articles are really helpful - Thank you.

  17. Ognyan Dimitrov Avatar

    Hello, I have found that Mr. Winters already did this here au.autodesk.com/?nd=class&session_id=3097 in his "Advanced AutoCAD® Jigs Workshop". Now I am modifying it for our purposes.

  18. Great - thanks for letting me know.

    Kean

  19. Ognyan Dimitrov Avatar

    I am watching now the movie about the associative surfaces and it is incredible advancement. The parameters network is very smart. This is a way to parametrize everything - like expanding the dynamic block idea to everything drawable. It is great job done by great professionals.

  20. Hello Kean:
    I have a question a with attributed block.
    I create a BlockTableRecord, and then, create a blockReference of it, then, i create a attributeReference, and add it to the blockReference's attributeCollection.
    Now, the block itself appear on the screen, but, the attribute does not show up until I select the block.

    Could you tell me why? How can I solve it?

  21. Hello cairunbin,

    Sorry - I really don't have time to help with this.

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

    Regards,

    Kean

  22. thank you, Kean.

  23. the problem solved.
    I forgot the following sentence:

    tr.AddNewlyCreatedDBObject(ar, true);

    Thank you , kean.

  24. Hy everybody.

    In november I wrote code to manage Jig (blocks and attributes) and it worked fine on Autocad 2010.
    On december, the company I work for, updated to autocad 2011.
    Now, the application works again (I updated ObjectARX for Autocad 2011) but Jig doesn't shows object and attributes even if when I click the block is insered in the dwg.
    The strange thing is that I debugged the application (Visula Studio 2011) and if I Step trought the code, I can see that Jig works (it shows object and attributes) but if I press F5 it doesen't shows block and attributes but works because inserts the block in the dwg. It seems a refresh problem.

    How can I solve It?

    Thank you.

  25. Hi Corrado,

    This code works fine for me on AutoCAD 2011. Have you tested your application on other systems, to see whether it's specific to your development machine?

    If this doesn't help, you might try posting your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  26. I tried the compiled code on another machine, but the problem seems to be the same. It works but doesn't show the jig.
    I'l try posting my question to AutoCAD discussion.

    Thank you for your help.

    Corrado

  27. I think I finally found the problem.
    I wrote a stand alone test code, and it works fine (as you said).
    When I put together the test and my application code, it works but doesn't shows jig functionality.

    The problem seems to be that I run the jig code from a docking palette in my application!

    May be Autocad 2011 doesn't manage this case, because in 2009 and 2010 it works fine.

    Now I'm looking for a solution to this problem...

    Thank you

    Corrado

  28. Hi Corrado,

    Try putting your code inside and command and use SendStringToExecute() to launch it from the modeless dialog: that alone will solve a whole load of problems - this is the approach I always take when driving AutoCAD from a PaletteSet.

    Regards,

    Kean

  29. Hi Kean,

    I've come across a situation where it appears that ar.SetAttributeFromBlock(ad, br.BlockTransform) is all that is needed to properly insert the attribute to a block regardless of alignment/mtext. Can you shed some light on what is really happening here?

    theswamp.org/index.php?topic=42226.0

    Would be very appreciated!

  30. Hi Will,

    This particular post relates fairly specifically to jigging: the main point is that you need to add the block to the database for the jigging operation to work.

    It's possible that SetAttributeFromBlock() might also have been used to help with this particular case, to set up the block prior to jigging: the code was provided by Philippe, so I'd have to ask him (and it was some time ago), or spend some time looking into it myself.

    It sounds like what you have is working well for your purposes. Although perhaps I've missed the point?

    Regards,

    Kean

  31. Thanks Kean,

    You've hit it square on the head, it's just a matter of an apparent anomaly I was hoping someone at AutoDesk might have the answer to. Seeing as you guided much of my learning curve I came here first. I've since come across another one with objects still being open for write after leaving the scope of a transaction. I'll save you the time of reiterating this is not a support forum and post this on ADN.

    Thanks

  32. plus.google.com/11081674282273 Avatar
    plus.google.com/11081674282273

    Thank you, Kean!
    I write code (C++) based on your post with extended functions (manipulating visibility parameter and changing attribute value "on the fly", etc.).
    Here is sources:
    drive.google.com/file/d/0B3twxy3GeGYzR2pibjBhQWVsREU/edit?usp=sharing

    P.S. some comments in .h file...
    P.P.S. Code is a little bit dirty, but works perfectly (for my needs).

  33. Glad you found it useful. 🙂

    Thanks for sharing!

    Kean

  34. Hello Kean,

    Thank you for this example. With it I can finally move forward with user assisted inserts.

    I am experiencing odd behavior which I feel may be an AutoCAD bug and I wanted to see if you have run into this as well.

    When I jig a block with an attribute and that attributes default value is longer than one character the attribute in the jig is placed incorrectly. It is offset to the right. Once the user clicks it is restored.

    I get similar, yet varied behavior when doing a raw (no custom code & it offsets to the left) insert command which is why I feel it is a bug.

    Have you experienced this as well, and do you know of a method to compensate?

    Thanks,
    Jeff

    1. Hello Jeff,

      Have you experience this issue with the above code? I don't recall having seen this specifically, but then I've worked through a lot of text justification issues over the years.

      As it seems to be reproducible with the INSERT command... if you can email me a simple DWG with some steps to reproduce I'll take a quick look and let you know what I think.

      Regards,

      Kean

  35. The line:

    ar.Position = ai.Position.TransformBy(br.BlockTransform);

    Should be replaced by:

    ar.Position = ai.Position + br.BlockTransform.Translation;

    Because if the block reference has a scale != 1, the scale is reapplied each time.

  36. Thanks, Maxence. As mentioned in the last update, this post has been superceded by this one:

    keanw.com/2015/05/jigging-an-autocad-block-with-attributes-using-net-redux.html

    Please do let me know if that also has the same problem.

    Kean

Leave a Reply to Maxence Cancel reply

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