October 2014

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  










« AutoCAD WS | Main | Photo Scene Editor Competition »

September 09, 2010

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 =

            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.

blog comments powered by Disqus

Feed/Share

10 Random Posts