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”).

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

{

publicclassQRCodeApplication : IExtensionApplication

{

publicvoid Initialize()

{

try

{

RegistryUpdate.RegisterForDemandLoading();

}

catch

{ }

}

publicvoid Terminate()

{

}

// Base record name, also used as regApp name

conststring recBase = "ADNP_QR";

classSquareRasterJig : EntityJig

{

Matrix3d _ucs;

Point3d _start = Point3d.Origin;

Point3d _end = Point3d.Origin;

public SquareRasterJig(

ObjectId defId,

Matrix3d ucs,

Point3d start

) : base(newRasterImage())

{

_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 =

newCoordinateSystem3d(

_start,

newVector3d(size, 0, 0),

newVector3d(0, size, 0)

);

ri.ShowImage = true;

}

protectedoverrideSamplerStatus Sampler(

JigPrompts prompts

)

{

JigPromptPointOptions opts =

newJigPromptPointOptions();

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)

{

returnSamplerStatus.NoChange;

}

else

{

_end = tmp;

returnSamplerStatus.OK;

}

}

returnSamplerStatus.Cancel;

}

protectedoverridebool 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)

returnfalse;

// 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 = newVector3d(size, 0, 0);

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

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

orig = _start;

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

orig = _start + newVector3d(-size, 0, 0);

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

orig = _start + newVector3d(0, -size, 0);

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

orig = _start - newVector3d(size, size, 0);

// Set the image's orientation in WCS

ri.Orientation =

newCoordinateSystem3d(

orig.TransformBy(_ucs),

xAxis.TransformBy(_ucs),

yAxis.TransformBy(_ucs)

);

returntrue;

}

publicEntity GetEntity()

{

return Entity;

}

}

// Create a QR Code

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

staticpublicvoid 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 = newRasterImageDef();

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 =

newSquareRasterJig(

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)]

staticpublicvoid 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)

asRasterImage;

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)]

staticpublicvoid 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)]

staticpublicvoid 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)]

staticpublicvoid 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

staticprivatestring 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)

asRasterImage;

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

staticprivateObjectId GetQrCode(Document doc)

{

PromptEntityOptions peo =

newPromptEntityOptions("\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)

returnObjectId.Null;

return per.ObjectId;

}

// Helper function to add a registered application

staticvoid 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 =

newRegAppTableRecord();

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]

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.