Jigging an upright, square, AutoCAD raster image using .NET

Following on from these two posts, we're now going to implement a jig to create our QR Code raster images inside AutoCAD.

Today's approach isn't radically different from the previous one prompting selection of corners, but we do get to see the actual (square) boundary of the raster object as it gets defined. It would have been even better if the raster contents were displayed during the jig, but from what I can tell this has been disabled deliberately, no doubt for performance reasons (you see the same effect – only having the boundary visible – when you move or edit a raster via its grips, for instance). I had hoped that, just like for BlockReferences and Solid3d objects, adding the raster image to the drawing before jigging it – and so passing the RasterImage to the jig, rather than the ObjectId of the RasterImageDef – would cause the contents to be displayed in full, but unfortunately it didn't change anything. So I backed out those changes, and have continued with the typical, more transient approach to jigging.

Here's the updated C# code, with new/updated lines in red (here's the source file for download):

    1 using Autodesk.AutoCAD.Runtime;

    2 using Autodesk.AutoCAD.ApplicationServices;

    3 using Autodesk.AutoCAD.DatabaseServices;

    4 using Autodesk.AutoCAD.EditorInput;

    5 using Autodesk.AutoCAD.Geometry;

    6 using System;

    7

    8 namespace QRCodeApplication

    9 {

   10   public class Commands

   11   {

   12     class SquareRasterJig : EntityJig

   13     {

   14       Matrix3d _ucs;

   15       Point3d _start = Point3d.Origin;

   16       Point3d _end = Point3d.Origin;

   17

   18       public SquareRasterJig(

   19         ObjectId defId,

   20         Matrix3d ucs,

   21         Point3d start

   22       ) : base(new RasterImage())

   23       {

   24         _start = start;

   25         _ucs = ucs;

   26

   27         RasterImage ri = (RasterImage)Entity;

   28         ri.ImageDefId = defId;

   29

   30         // Create a near zero size default image,

   31         // to avoid the boundary flicker

   32

   33         double size = Tolerance.Global.EqualPoint;

   34         ri.Orientation =

   35           new CoordinateSystem3d(

   36             _start,

   37             new Vector3d(size, 0, 0),

   38             new Vector3d(0, size, 0)

   39           );

   40         ri.ShowImage = true;

   41       }

   42

   43       protected override SamplerStatus Sampler(

   44         JigPrompts prompts

   45       )

   46       {

   47         JigPromptPointOptions opts =

   48           new JigPromptPointOptions();

   49         opts.UserInputControls =

   50           (UserInputControls.Accept3dCoordinates |

   51           UserInputControls.NoNegativeResponseAccepted);

   52         opts.Message = "\nSecond corner of QR Code: ";

   53

   54         // Get the point itself

   55

   56         PromptPointResult res = prompts.AcquirePoint(opts);

   57

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

   59         {

   60           // Convert the supplied point into UCS

   61

   62           Point3d tmp =

   63             res.Value.TransformBy(_ucs.Inverse());

   64

   65           // Check if changed (reduces flicker)

   66

   67           if (_end == tmp)

   68           {

   69             return SamplerStatus.NoChange;

   70           }

   71           else

   72           {

   73             _end = tmp;

   74             return SamplerStatus.OK;

   75           }

   76         }

   77         return SamplerStatus.Cancel;

   78       }

   79

   80       protected override bool Update()

   81       {

   82         RasterImage ri = (RasterImage)Entity;

   83

   84         // Get offset between the two corners

   85

   86         Vector3d diff = _end - _start;

   87

   88         // Get the smallest of the X and Y

   89         // (could also be the largest - this is a choice)

   90

   91         double size =

   92           Math.Min(Math.Abs(diff.X), Math.Abs(diff.Y));

   93

   94         // If we're at zero size, don't update

   95

   96         if (size < Tolerance.Global.EqualPoint)

   97           return false;

   98

   99         // Determing the image's orientation...

  100

  101         // The original will depend on the order of the corners

  102         // It will be offset to the left and/or down depending

  103         // on the values of the vector between the two points

  104

  105         Point3d orig;

  106

  107         // The axes stay the same, as we will always keep the

  108         // image oriented the same way relative to the UCS

  109

  110         Vector3d xAxis = new Vector3d(size, 0, 0);

  111         Vector3d yAxis = new Vector3d(0, size, 0);

  112

  113         if (diff.X > 0 && diff.Y > 0) // Dragging top-right

  114           orig = _start;

  115         else if (diff.X < 0 && diff.Y > 0) // Top-left

  116           orig = _start + new Vector3d(-size, 0, 0);

  117         else if (diff.X > 0 && diff.Y < 0) // Bottom-right

  118           orig = _start + new Vector3d(0, -size, 0);

  119         else // if (diff.X < 0 && diff.Y < 0) // Bottom-left

  120           orig = _start - new Vector3d(size, size, 0);

  121

  122         // Set the image's orientation in WCS

  123

  124         ri.Orientation =

  125           new CoordinateSystem3d(

  126             orig.TransformBy(_ucs),

  127             xAxis.TransformBy(_ucs),

  128             yAxis.TransformBy(_ucs)

  129
          );

  130

  131         return true;

  132       }

  133

  134       public Entity GetEntity()

  135       {

  136         return Entity;

  137       }

  138     }

  139

  140     [CommandMethod("QR")]

  141     static public void QRCode()

  142     {

  143       // Base record name and URL constants

  144

  145       const string recBase = "ADNP_QR";

  146       const string rootUrl =

  147         "http://chart.apis.google.com/chart?cht=qr&chs=500x500&chl=";

  148

  149       Document doc =

  150         Application.DocumentManager.MdiActiveDocument;

  151       Database db = doc.Database;

  152       Editor ed = doc.Editor;

  153

  154       PromptResult pr =

  155         ed.GetString("\nEnter email address to encode: ");

  156       if (pr.Status != PromptStatus.OK)

  157         return;

  158

  159       // Encode the colon and the @ symbol for the URL

  160

  161       string message =

  162         "mailto%3A" + pr.StringResult.Replace("@", "%40");

  163


164
       Transaction tr =

  165         doc.TransactionManager.StartTransaction();

  166       using (tr)

  167       {

  168         ObjectId dictId =

  169           RasterImageDef.GetImageDictionary(db);

  170

  171         if (dictId.IsNull)

  172         {

  173           // Image dictionary doesn't exist, create new

  174

  175           dictId =

  176             RasterImageDef.CreateImageDictionary(db);

  177         }

  178

  179         // Open the image dictionary

  180

  181         DBDictionary dict =

  182           (DBDictionary)tr.GetObject(

  183             dictId,

  184             OpenMode.ForRead

  185           );

  186

  187         // Get a unique record name for our raster image

  188         // definition

  189

  190         int i = 0;

  191         string recName = recBase + i.ToString();

  192

  193         while (dict.Contains(recName))

  194         {

  195           i++;

  196           recName = recBase + i.ToString();

  197         }

  198

  199         RasterImageDef rid = new RasterImageDef();

  200

  201         // Set its source image

  202

  203         rid.SourceFileName = rootUrl + message;

  204
60;

  205         // Load it

  206

  207         rid.Load();

  208         dict.UpgradeOpen();

  209

  210         ObjectId defId = dict.SetAt(recName, rid);

  211

  212         // Let the transaction know

  213

  214         tr.AddNewlyCreatedDBObject(rid, true);

  215

  216         PromptPointResult ppr =

  217           ed.GetPoint("\nFirst corner of QR Code: ");

  218         if (ppr.Status != PromptStatus.OK)

  219           return;

  220

  221         // Call our jig to define the raster

  222

  223         SquareRasterJig jig =

  224           new SquareRasterJig(

  225             defId,

  226             ed.CurrentUserCoordinateSystem,

  227             ppr.Value

  228           );

  229         PromptResult prj = ed.Drag(jig);

  230

  231         if (prj.Status != PromptStatus.OK)

  232         {

  233           rid.Erase();

  234           return;

  235         }

  236

  237         // Get our entity and add it to the modelspace

  238

  239         RasterImage ri = (RasterImage)jig.GetEntity();

  240

  241         BlockTable bt =

  242           (BlockTable)tr.GetObject(

  243             db.BlockTableId,

  244             OpenMode.ForRead

  245           );

  246

  247         BlockTableRecord btr =

  248           (BlockTableRecord)tr.GetObject(

  249             bt[BlockTableRecord.ModelSpace],

  250             OpenMode.ForWrite

  251           );

  252

  253         btr.AppendEntity(ri);

  254         tr.AddNewlyCreatedDBObject(ri, true);

  255

  256         // Create a reactor between the RasterImage and the

  257         // RasterImageDef to avoid the "unreferenced"

  258         // warning in the XRef palette

  259

  260         RasterImage.EnableReactors(true);

  261         ri.AssociateRasterDef(rid);

  262

  263         // Let's add our message string as XData,

  264         // in case we need it later

  265

  266         AddRegAppTableRecord("ADNP_QR");

  267         ResultBuffer rb =

  268           new ResultBuffer(

  269             new TypedValue(1001, "ADNP_QR"),

  270             new TypedValue(1000, message)

  271           );

  272         ri.XData = rb;

  273         rb.Dispose();

  274

  275         tr.Commit();

  276       }

  277     }

  278

  279     static void AddRegAppTableRecord(string regAppName)

  280     {

  281       Document doc =

  282         Application.DocumentManager.MdiActiveDocument;

  283       Editor ed = doc.Editor;

  284       Database db = doc.Database;

  285

  286       Transaction tr =

  287         doc.TransactionManager.StartTransaction();

  288       using (tr)

  289       {

  290         RegAppTable rat =

  291           (RegAppTable)tr.GetObject(

  292             db.RegAppTableId,

  293             OpenMode.ForRead,

  294             false

  295           );

  296         if (!rat.Has(regAppName))

  297         {

  298           rat.UpgradeOpen();

  299           RegAppTableRecord ratr =

  300             new RegAppTableRecord();

  301           ratr.Name = regAppName;

  302           rat.Add(ratr);

  303           tr.AddNewlyCreatedDBObject(ratr, true);

  304         }

  305         tr.Commit();

  306       }

  307     }

  308   }

  309 }

The changes themselves should be reasonably clear: the code defining the size of the boundary has been moved to the jig, and we're now using the jig to manage the user input.

Here's what happens when we use our updated QR command to jig the position and size of a QR Code:

Jigging our square, QR Code raster image

Our jigged QR Code raster image

I haven't implemented rotation, as it's not clear to me that it needs to be an integrated part of the QR command. If it's only needed rarely – my current expectation – it's probably better to leave it to an existing command such as PROPERTIES or ROTATE.

The next stage in this application's development is to take a look at some kind of UI for defining different sets of content, as well as the ability to edit the contents of an existing QR Code.

2 responses to “Jigging an upright, square, AutoCAD raster image using .NET”

  1. Kean,

    Since this is the most recent post filed under Jigs I'll ask my question here.

    What is accepted good practice for setting the UserInputControls flags? I notice that your own usage has evolved over time, from

    Accept3dCoordinates |
    NoZeroResponseAccepted |
    NoNegativeResponseAccepted

    in May '07 (keanw.com/2007/05/using_a_jig_fro_1.html) to

    NoZeroResponseAccepted

    in March '09 (keanw.com/2009/03/jigging-an-autocad-block-with-attributes-using-net.html) to

    Accept3dCoordinates |
    AcceptMouseUpAsPoint |
    GovernedByUCSDetect

    in November '09 (keanw.com/2009/11/detecting-the-use-of-the-shift-and-control-keys-during-an-autocad-jig-using-net.html) and finally

    Accept3dCoordinates |
    NoNegativeResponseAccepted

    in this post.

    Are there specific behaviors you're trying to get with the different combinations in your posts referenced above?

  2. Chuck,

    I'd like to say that the choice of flags was consistently driven by the needs of each jig, but it's also possible that the specifics may have varied because a) that's what seemed logical to me at the time, b) that's what worked when I tested it or even c) it came from a snippet that I picked up from somewhere else (the least likely scenario).

    I unfortunately don't have time to dig into the specific details, right now: you might try submitting a request to the ADN team, if you're a member, or otherwise posting to the AutoCAD .NET Discussion Group.

    Kean

Leave a Reply

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