As promised, the code in this post extends that from the last post to add some user selection to the process of defining and placing a QR Code in an AutoCAD drawing.
This version of the code also makes sure the raster is positioned in an “upright” orientation, irrespective of the order in which the corners are selected. Many images need to be positioned in such a way (although QR Codes are presumably more tolerant due to the positioning squares in three of the corners), so I expect this technique will be of interest to various people who need to place images at user-specified locations. QR Codes are also square, so we’re choosing the smallest of the axes to define size (we could very easily also take the largest). This approach will become even more interesting when we migrate it to a jig, so we actually see a square area during the selection process.
One final change: I’ve used code from this previous post to add the message string as XData to the raster image. This will allow us to provide some kind of interface for updating the string, in the future (should we need to). Although we could probably also divine this from the image's URL, as an alternative approach.
Here’s the updated C# code, with modified or new 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 [CommandMethod("QR")]
13 static public void QRCode()
14 {
15 // Base record name and URL constants
16
17 const string recBase = "ADNP_QR";
18 const string rootUrl =
19 "http://chart.apis.google.com/chart?cht=qr&chs=500x500&chl=";
20
21 Document doc =
22 Application.DocumentManager.MdiActiveDocument;
23 Database db = doc.Database;
24 Editor ed = doc.Editor;
25
26 PromptResult pr =
27 ed.GetString("\nEnter email address to encode: ");
28 if (pr.Status != PromptStatus.OK)
29 return;
30
31 // Encode the colon and the @ symbol for the URL
32
33 string message =
34 "mailto%3A" + pr.StringResult.Replace("@", "%40");
35
36 Transaction tr =
37 doc.TransactionManager.StartTransaction();
38 using (tr)
39 {
40 ObjectId dictId =
41 RasterImageDef.GetImageDictionary(db);
42
43 if (dictId.IsNull)
44 {
45 // Image dictionary doesn't exist, create new
46
47 dictId =
48 RasterImageDef.CreateImageDictionary(db);
49 }
50
51 // Open the image dictionary
52
53 DBDictionary dict =
54 (DBDictionary)tr.GetObject(
55 dictId,
56 OpenMode.ForRead
57 );
58
59 // Get a unique record name for our raster image
60 // definition
61
62 int i = 0;
63 string recName = recBase + i.ToString();
64
65 while (dict.Contains(recName))
66 {
67 i++;
68 recName = recBase + i.ToString();
69 }
70
71 RasterImageDef rid = new RasterImageDef();
72
73 // Set its source image
74
75 rid.SourceFileName = rootUrl + message;
76
77 // Load it
78
79 rid.Load();
80 dict.UpgradeOpen();
81
82 ObjectId defId = dict.SetAt(recName, rid);
83
84 // Let the transaction know
85
86 tr.AddNewlyCreatedDBObject(rid, true);
87
88 PromptPointResult ppr =
89 ed.GetPoint("\nFirst corner of QR Code: ");
90 if (ppr.Status != PromptStatus.OK)
91 return;
92
93 Point3d start = ppr.Value;
94
95 ppr =
96 ed.GetCorner("\nSecond corner of QR Code: ", start);
97 if (ppr.Status != PromptStatus.OK)
98 return;
99
100 // Get offset between the two corners
101
102 Vector3d diff = ppr.Value - start;
103
104 // Get the smallest of the X and Y
105 // (could also be the largest - this is a choice)
106
107 double size =
108 Math.Min(Math.Abs(diff.X), Math.Abs(diff.Y));
109
110 // If we're at zero size, don't update
111
112 if (size < Tolerance.Global.EqualPoint)
113 return;
114
115 // Determing the image's orientation...
116
117 // The original will depend on the order of the corners
118 // It will be offset to the left and/or down depending
119 // on the values of the vector between the two points
120
121 Point3d orig;
122
123 // The axes stay the same, as we will always keep the
124 // image oriented the same way relative to the UCS
125
126 Vector3d xAxis = new Vector3d(size, 0, 0);
127 Vector3d yAxis = new Vector3d(0, size, 0);
128
129 if (diff.X > 0 && diff.Y > 0) // Dragging top-right
130 orig = start;
131 else if (diff.X < 0 && diff.Y > 0) // Top-left
132 orig = start + new Vector3d(-size, 0, 0);
133 else if (diff.X > 0 && diff.Y < 0) // Bottom-right
134 orig = start + new Vector3d(0, -size, 0);
135 else // if (diff.X < 0 && diff.Y < 0) // Bottom-left
136 orig = start - new Vector3d(size, size, 0);
137
138 // Create the raster image that references the
139 // definition
140
141 RasterImage ri = new RasterImage();
142 ri.ImageDefId = defId;
143 ri.ShowImage = true;
144
145 // Set the image's orientation in WCS
146
147 Matrix3d ucs = ed.CurrentUserCoordinateSystem;
148
149 ri.Orientation =
150 new CoordinateSystem3d(
151 orig.TransformBy(ucs),
152 xAxis.TransformBy(ucs),
153 yAxis.TransformBy(ucs)
154 );
155
156 BlockTable bt =
157 (BlockTable)tr.GetObject(
158 db.BlockTableId,
159 OpenMode.ForRead
160 );
161
162 BlockTableRecord btr =
163 (BlockTableRecord)tr.GetObject(
164 bt[BlockTableRecord.ModelSpace],
165 OpenMode.ForWrite
166 );
167
168 btr.AppendEntity(ri);
169 tr.AddNewlyCreatedDBObject(ri, true);
170
171 // Create a reactor between the RasterImage and the
172 // RasterImageDef to avoid the "unreferenced"
173 // warning in the XRef palette
174
175 RasterImage.EnableReactors(true);
176 ri.AssociateRasterDef(rid);
177
178 // Let's add our message string as XData,
179 // in case we need it later
180
181 AddRegAppTableRecord("ADNP_QR");
182 ResultBuffer rb =
183 new ResultBuffer(
184 new TypedValue(1001, "ADNP_QR"),
185 new TypedValue(1000, message)
186 );
187 ri.XData = rb;
188 rb.Dispose();
189
190 tr.Commit();
191 }
192 }
193
194 static void AddRegAppTableRecord(string regAppName)
195 {
196 Document doc =
197 Application.DocumentManager.MdiActiveDocument;
198 Editor ed = doc.Editor;
199 Database db = doc.Database;
200
201 Transaction tr =
202 doc.TransactionManager.StartTransaction();
203 using (tr)
204 {
205 RegAppTable rat =
206 (RegAppTable)tr.GetObject(
207 db.RegAppTableId,
208 OpenMode.ForRead,
209 false
210 );
211 if (!rat.Has(regAppName))
212 {
213 rat.UpgradeOpen();
214 RegAppTableRecord ratr =
215 new RegAppTableRecord();
216 ratr.Name = regAppName;
217 rat.Add(ratr);
218 tr.AddNewlyCreatedDBObject(ratr, true);
219 }
220 tr.Commit();
221 }
222 }
223 }
224 }
Let’s take a look at the individual changes:
- Line 6
- Add the System namespace for the various Math functions we’re now using
- Lines 24-34
- Query the user for the email address to encode
- Lines 88-136
- Query for the location of the image, manipulate the position of the image so that it's "upright"
- Lines 145-154
- Transform the orientation relative to the current UCS
- Lines 178-188 & 194-222
- Add the message string as XData, so we can come back and get it/update it in the future
We can see that as we run the QR command we can enter an email address to encode (we’ll extend this UI to cover other types of relevant information, at some point) as well as the location of the QR Code. Irrespective of the order of corner selection, the raster is always square and upright: