More fun with QR Codes: encoding different types of data inside AutoCAD

After a break of a week, I thought it was time to take the QR Code application a little further, after our previous versions creating basic QR codes at a fixed location, using user-specified corners and using a jig.

The code in this post adds quite a bit of functionality to the application:

  • The ability to encode various types of data
    • Calendar events
    • Contact information
    • Email addresses
    • Geo-locations
    • Phone numbers
    • Plain text
    • URLs
  • The ability to edit QR Codes
    • The data used to create the QR Code is attached to the raster image as XData and gets used for default values
  • The ability to query the URL of an existing QR Code
    • i.e. the Google Code URL used to create the raster image
  • The ability to decode an existing QR Code
    • The Google ZXing Decoder is launched automatically with the URL of the selected QR Code
    • Handy for checking the QR Code just after creation

The UI is still command-line based, but it's altogether possible to bolt on a fancy GUI, should one so wish (and I may well extend that in a future post, we'll see). In the main part the UI options have been modelled after those available on the ZXing Generator (leaving out a few data-types that don't seem very relevant, such as "SMS" and "WiFi network").

The source project now contains a number of different C# files:

  • Commands.cs โ€“ our command definitions
  • QrInput.cs โ€“ user input functions for our command-line data entry
  • QrEncoder.cs โ€“ encoder functions to generate a Google Chart URL for a set of data
  • RbEncoder.cs โ€“ encoder functions to create a ResultBuffer from the user-specified data
  • demand-loading.cs โ€“ our usual file to auto-generate demand-loading Registry keys

There's nothing very remarkable about the code: it did occur to me as I was hard-coding the input paths for the various data-types that it might be interesting to generalise the mechanism to use some kind of "schema" of the various data fields and use a more dynamic approach to query and encode the information. But I decided not to go down that path, for now, at least.

I decided to implement a custom exception type in QrInput.cs to denote user cancellation, mainly because it was proving cumbersome to force the return type to encode that condition in some way (such as a null string or Double.NaN), and I didn't want to fall back on ref or out parameters. So when the user cancels we accept the overhead of throwing and handling an exception, which certainly makes the code simpler.

There's still potential for replacing the generation of the QR Codes themselves to be performed locally, rather than via a web service. I think โ€“ overall โ€“ that the benefits of the current approach do outweigh the effort required to use a local QR Code library, but this is certainly something that could change (feedback welcome!).

As the code now spans multiple files I won't list them all, but here is the main Commands.cs file:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using System;

using DemandLoading;

 

namespace QRCodes

{

  public class QRCodeApplication : IExtensionApplication

  {

    public void Initialize()

    {

      try

      {

        RegistryUpdate.RegisterForDemandLoading();

      }

      catch

      { }

    }

 

    public void Terminate()

    {

    }

 

    // Base record name, also used as regApp name

 

    const string recBase = "ADNP_QR";

 

    class SquareRasterJig : EntityJig

    {

      Matrix3d _ucs;

      Point3d _start = Point3d.Origin;

      Point3d _end = Point3d.Origin;

 

      public SquareRasterJig(

        ObjectId defId,

        Matrix3d ucs,

        Point3d start

      ) : base(new RasterImage())

      {

        _start = start;

        _ucs = ucs;

 

        RasterImage ri = (RasterImage)Entity;

        ri.ImageDefId = defId;

 

        // Create a near zero size default image,

        // to avoid the boundary flicker

 

        double size = Tolerance.Global.EqualPoint;

        ri.Orientation =

          new CoordinateSystem3d(

            _start,

            new Vector3d(size, 0, 0),

            new Vector3d(0, size, 0)

          );

        ri.ShowImage = true;

      }

 

      protected override SamplerStatus Sampler(

        JigPrompts prompts

      )

      {

        JigPromptPointOptions opts =

          new JigPromptPointOptions();

        opts.UserInputControls =

          (UserInputControls.Accept3dCoordinates |

          UserInputControls.NoNegativeResponseAccepted);

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

 

        // Get the point itself

 

        PromptPointResult res = prompts.AcquirePoint(opts);

 

        if (res.Status == PromptStatus.OK)

        {

          // Convert the supplied point into UCS

 

          Point3d tmp =

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

 

          // Check if changed (reduces flicker)

 

          if (_end == tmp)

          {

            return SamplerStatus.NoChange;

          }

          else

          {

            _end = tmp;

            return SamplerStatus.OK;

          }

        }

        return SamplerStatus.Cancel;

      }

 

      protected override bool Update()

      {

        RasterImage ri = (RasterImage)Entity;

 

        // Get offset between the two corners

 

        Vector3d diff = _end - _start;

 

        // Get the smallest of the X and Y

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

 

        double size =

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

 

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

 

        if (size < Tolerance.Global.EqualPoint)

          return false;

 

        // Determing the image's orientation...

 

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

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

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

 

        Point3d orig;

 

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

        // image oriented the same way relative to the UCS

 

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

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

 

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

          orig = _start;

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

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

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

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

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

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

 

        // Set the image's orientation in WCS

 

        ri.Orientation =

          new CoordinateSystem3d(

            orig.TransformBy(_ucs),

            xAxis.TransformBy(_ucs),

            yAxis.TransformBy(_ucs)

          );

 

        return true;

      }

 

      public Entity GetEntity()

      {

        return Entity;

      }

    }

 

    //
Create a QR Code

 

    [CommandMethod("ADNPLUGINS", "QR", CommandFlags.Modal)]

    static public void QRCode()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

      ResultBuffer rb;

 

      // Get the data from the user and encode it into a URL

 

      string url =

        QrInput.GetUrlForQrCode(ed, null, out rb);

 

      if (String.IsNullOrEmpty(url))

        return;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        // Get the image dictionary's ID, if it already

        // exists

 

        ObjectId dictId =

          RasterImageDef.GetImageDictionary(db);

 

        if (dictId.IsNull)

        {

          // If it doesn't, create a new one

 

          dictId =

            RasterImageDef.CreateImageDictionary(db);

        }

 

        // Open the image dictionary

 

        DBDictionary dict =

          (DBDictionary)tr.GetObject(

            dictId,

            OpenMode.ForRead

          );

 

        // Get a unique record name for our raster image

        // definition

 

        int i = 0;

        string recName = recBase + i.ToString();

 

        while (dict.Contains(recName))

        {

          i++;

          recName = recBase + i.ToString();

        }

 

        RasterImageDef rid = new RasterImageDef();

 

        try

        {

          // Set its source image

 

          rid.SourceFileName = url;

 

          // Load it

 

          rid.Load();

        }

        catch

        {

          ed.WriteMessage(

            "\nUnable to create image object. " +

            "Here is the URL to the image: {0}",

            url

          );

          return;

        }

 

        // Put the definition in the dictionary

 

        dict.UpgradeOpen();

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

 

        // Let the transaction know about it

 

        tr.AddNewlyCreatedDBObject(rid, true);

 

        // Now we start the placement of the RasterImage

 

        PromptPointResult ppr =

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

        if (ppr.Status != PromptStatus.OK)

          return;

 

        // Call our jig to place the raster

 

        SquareRasterJig jig =

          new SquareRasterJig(

            defId,

            ed.CurrentUserCoordinateSystem,

            ppr.Value

          );

        PromptResult prj = ed.Drag(jig);

 

        // If it was cancelled then return

        // (will abort the transaction)

 

        if (prj.Status != PromptStatus.OK)

          return;

 

        // Get our entity and add it to the current space

 

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

 

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForWrite

          );

 

        btr.AppendEntity(ri);

        tr.AddNewlyCreatedDBObject(ri, true);

 

        // Create a reactor between the RasterImage and the

        // RasterImageDef to avoid the "unreferenced"

        // warning in the XRef palette

 

        RasterImage.EnableReactors(true);

        ri.AssociateRasterDef(rid);

 

        // Let's add our message information as XData,

        // for later editing

 

        AddRegAppTableRecord(recBase);

        ri.XData = rb;

        rb.Dispose();

 

        tr.Commit();

      }

    }

 

    // Edit a QR Code using originally entered data defaults

 

    [CommandMethod("ADNPLUGINS", "QRE", CommandFlags.Modal)]

    static public void QREdit()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Select a QR Code for editing

 

      ObjectId riId = GetQrCode(doc);

      if (riId == ObjectId.Null)

        return;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        RasterImage ri =

          tr.GetObject(riId, OpenMode.ForRead)

            as RasterImage;

        if (ri != null)

        {

          // Get the RasterImage's XData

 

          ResultBuffer rb =

            ri.GetXDataForApplication(recBase);

 

          // Now call the same input routine as

          // when creating, but pass our XData defaults

 

          ResultBuffer rb2;

          string url =

            QrInput.GetUrlForQrCode(ed, rb, out rb2);

          rb.Dispose();

 

          if (String.IsNullOrEmpty(url))

            return;

 

          // If we have a valid string returned, set

          // it on the RasterImageDef

 

          RasterImageDef rid =

            (RasterImageDef)tr.GetObject(

              ri.ImageDefId,

              OpenMode.ForWrite

            );

 

          rid.SourceFileName = url;

          rid.Load();

 

          // Store the new defaults as XData

 

          ri.UpgradeOpen();

          ri.XData = rb2;

          rb2.Dispose();

        }

        tr.Commit();

      }

    }

 

    // Print the URL for a particular QR Code

 

    [CommandMethod("ADNPLUGINS", "QRU", CommandFlags.Modal)]

    static public void QRUrl()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      string url = GetQrCodeUrl(doc);

      if (!String.IsNullOrEmpty(url))

      {

        ed.WriteMessage("\nUrl: {0}", url);

      }

    }

 

    // Decode a QR Code in the drawing via the ZXing decoder

 

    [CommandMethod("ADNPLUGINS", "QRD", CommandFlags.Modal)]

    static public void QRDecode()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      string url = GetQrCodeUrl(doc);

      if (!String.IsNullOrEmpty(url))

      {

        System.Diagnostics.Process.Start(

          QrEncoder.EncodeQrCodeDecoderUrl(url)

        );

      }

    }

 

    // Unregister the application for future demand-loading

 

    [CommandMethod("ADNPLUGINS", "REMOVEQR", CommandFlags.Modal)]

    static public void RemoveQRCodes()

    {

      DemandLoading.RegistryUpdate.UnregisterForDemandLoading();

 

      Editor ed =

        Autodesk.AutoCAD.ApplicationServices.Application.

        DocumentManager.MdiActiveDocument.Editor;

 

      ed.WriteMessage(

        "\nThe QRCodes plugin will not be loaded" +

        " automatically in future editing sessions.");

    }

 

    // Helper function to select a QR Code and return its URL

 

    static private string GetQrCodeUrl(Document doc)

    {

      string res = null;

 

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Select a QR Code

 

      ObjectId riId = GetQrCode(doc);

      if (riId != ObjectId.Null)

      {

        Transaction tr =

          doc.TransactionManager.StartTransaction();

        using (tr)

        {

          RasterImage ri =

            tr.GetObject(riId, OpenMode.ForRead)

              as RasterImage;

          if (ri != null)

          {

            RasterImageDef rid =

              (RasterImageDef)tr.GetObject(

                ri.ImageDefId,

                OpenMode.ForRead

              );

 

            res = rid.SourceFileName;

          }

          tr.Commit();

        }

      }

      return res;

    }

 

    // Helper function to select a QR Code raster image

 

    static private ObjectId GetQrCode(Document doc)

    {

      PromptEntityOptions peo =

        new PromptEntityOptions("\nSelect QR Code: ");

      peo.SetRejectMessage("\nMust be a raster image.");

      peo.AddAllowedClass(typeof(RasterImage), true);

      PromptEntityResult per =

        doc.Editor.GetEntity(peo);

      if (per.Status != PromptStatus.OK)

        return ObjectId.Null;

      return per.ObjectId;

    }

 

    // Helper function to add a registered application

 

    static void AddRegAppTableRecord(string regAppName)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        RegAppTable rat =

          (RegAppTable)tr.GetObject(

            db.RegAppTableId,

            OpenMode.ForRead,

            false

          );

        if (!rat.Has(regAppName))

        {

          rat.UpgradeOpen();

          RegAppTableRecord ratr =

     
60;     
new RegAppTableRecord();

          ratr.Name = regAppName;

          rat.Add(ratr);

          tr.AddNewlyCreatedDBObject(ratr, true);

        }

        tr.Commit();

      }

    }

  }

}

When we run our QR command we can use the command-line interface to generate the various types of QR Code listed above:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: CA

Event title: This is an event

Start date & time: 1/1/11 12pm

End date & time(optional): 1/1/11 2pm

Location (optional): Somewhere sunny

Description (optional): Should be a blast

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: CO

Name: Kean Walmsley

Phone number (optional): +41 (32) 723-9499

Email address (optional): kean.walmsley@autodesk.com

Address (optional): Puits-Godet 6, Case Postale 35

Address 2 (optional): CH-2002 Switzerland

Website (optional): http://blogs.autodesk.com/through-the-interface

Memo (optional): Work details

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: E

Email address: kean.walmsley@autodesk.com

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: G

Latitude: 48.163415

Longitude: 11.480711

Query (optional): Autodesk Neuchatel

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: P

Phone number: +41 (32) 723-9499

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: T

Text: This is just some plain text. With punctuation, but nonetheless it's

pretty plain

First corner of QR Code:

Second corner of QR Code:

Command: QR

Type of data to encode [CAlendar/COntact/Email/Geolocation/Phone/Text/Url]

<Text>: U

Url: http://blogs.autodesk.com/through-the-interface

First corner of QR Code:

Second corner of QR Code:

Here are the various QR Codes inside AutoCAD:

QR Codes in AutoCAD

We can use the QRD command to check one at random. This passes the image's URL to ZXing, another Google service that decodes QR Codes:

Contact details decoded from a QR Code in AutoCAD

Update

A big thanks to Barry Ralphs for giving this a try and noticing that the QR command was not working in paperspace layouts. Which โ€“ with hindsight โ€“ is obvious, as it was adding it to the modelspace rather than the current space. I've gone ahead and updated the code (just one line changed, but I also removed a now-redundant opening of the BlockTable) and the project linked to above. My apologies to those of you who have already built it into an application.

8 responses to “More fun with QR Codes: encoding different types of data inside AutoCAD”

  1. This is cool Kean, thx.
    I'm trying to think of a way to incorporate the QR codes into our plotting system....
    Is this going to be a plugin of the month?

  2. That's the plan, Barry (probably in December).

    Please do let me know if you see any disadvantages with the use of rasters rather than native geometry, as you look more deeply into this.

    Kean

  3. Hi Kean,
    At first I was fine with them being raster, however I'm not sure I like the idea of them being stored online.
    If the service is ever down, then I'll loss my QR codes. And if there was a way to store them locally that would be great. Even better on a network location, so when someone else opens the file, it's still found.

    So I think I talked myself out of the raster idea, if they where just SOLID objects in a block stored in the DWG, then that would solve all the referencing issues..

    Also, I compiled you code above & the QR image doesn't show up when placed in paperspace, but it does show in the reference palette...

  4. Hi Kean,

    Nice plugin again. Instead of using
    a online service for creating the
    QR code you can use a library to
    generate them offline.
    Take a look at the follow page on
    the code project:
    codeproject.com/KB/cs/qrcode.aspx
    It's an open source library to encode
    and decode QR codes. With some extra effort
    one can create an jpg and attach it.

  5. Hi Barry,

    Thanks for taking a closer look.

    The images should get cached locally - they really only need the server to be up when they go to another machine. But yes, having them in an external file is less than ideal. It's just so much simpler to code and maintain. ๐Ÿ˜‰

    I'll take a look at the paperspace issue...

    Kean

  6. Hi Stephan,

    Thanks - I'm aware of a few such libraries... the existing implementation was done primarily for initial simplicity, as a proof of concept.

    I'm starting to lean towards looking at a fully embedded solution, but we'll see.

    Kean

  7. The next iPad better have a camera in it...
    I could foresee a plot system that uploads the DWG to Butterfly and adds a QR code URL to that file. Then when you're on-site with the hard-copy prints, you could edit that DWG on the spot as needed....

  8. OK - fixed. Thanks again for the feedback, Barry!

    Kean

Leave a Reply to Stephan Schevers Cancel reply

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