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:
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.


Leave a Reply