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