Following on from these two posts, we’re now going to implement a jig to create our QR Code raster images inside AutoCAD.
Today’s approach isn’t radically different from the previous one prompting selection of corners, but we do get to see the actual (square) boundary of the raster object as it gets defined. It would have been even better if the raster contents were displayed during the jig, but from what I can tell this has been disabled deliberately, no doubt for performance reasons (you see the same effect – only having the boundary visible – when you move or edit a raster via its grips, for instance). I had hoped that, just like for BlockReferences and Solid3d objects, adding the raster image to the drawing before jigging it – and so passing the RasterImage to the jig, rather than the ObjectId of the RasterImageDef – would cause the contents to be displayed in full, but unfortunately it didn’t change anything. So I backed out those changes, and have continued with the typical, more transient approach to jigging.
Here’s the updated C# code, with new/updated 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 class SquareRasterJig : EntityJig
13 {
14 Matrix3d _ucs;
15 Point3d _start = Point3d.Origin;
16 Point3d _end = Point3d.Origin;
17
18 public SquareRasterJig(
19 ObjectId defId,
20 Matrix3d ucs,
21 Point3d start
22 ) : base(new RasterImage())
23 {
24 _start = start;
25 _ucs = ucs;
26
27 RasterImage ri = (RasterImage)Entity;
28 ri.ImageDefId = defId;
29
30 // Create a near zero size default image,
31 // to avoid the boundary flicker
32
33 double size = Tolerance.Global.EqualPoint;
34 ri.Orientation =
35 new CoordinateSystem3d(
36 _start,
37 new Vector3d(size, 0, 0),
38 new Vector3d(0, size, 0)
39 );
40 ri.ShowImage = true;
41 }
42
43 protected override SamplerStatus Sampler(
44 JigPrompts prompts
45 )
46 {
47 JigPromptPointOptions opts =
48 new JigPromptPointOptions();
49 opts.UserInputControls =
50 (UserInputControls.Accept3dCoordinates |
51 UserInputControls.NoNegativeResponseAccepted);
52 opts.Message = "\nSecond corner of QR Code: ";
53
54 // Get the point itself
55
56 PromptPointResult res = prompts.AcquirePoint(opts);
57
58 if (res.Status == PromptStatus.OK)
59 {
60 // Convert the supplied point into UCS
61
62 Point3d tmp =
63 res.Value.TransformBy(_ucs.Inverse());
64
65 // Check if changed (reduces flicker)
66
67 if (_end == tmp)
68 {
69 return SamplerStatus.NoChange;
70 }
71 else
72 {
73 _end = tmp;
74 return SamplerStatus.OK;
75 }
76 }
77 return SamplerStatus.Cancel;
78 }
79
80 protected override bool Update()
81 {
82 RasterImage ri = (RasterImage)Entity;
83
84 // Get offset between the two corners
85
86 Vector3d diff = _end - _start;
87
88 // Get the smallest of the X and Y
89 // (could also be the largest - this is a choice)
90
91 double size =
92 Math.Min(Math.Abs(diff.X), Math.Abs(diff.Y));
93
94 // If we're at zero size, don't update
95
96 if (size < Tolerance.Global.EqualPoint)
97 return false;
98
99 // Determing the image's orientation...
100
101 // The original will depend on the order of the corners
102 // It will be offset to the left and/or down depending
103 // on the values of the vector between the two points
104
105 Point3d orig;
106
107 // The axes stay the same, as we will always keep the
108 // image oriented the same way relative to the UCS
109
110 Vector3d xAxis = new Vector3d(size, 0, 0);
111 Vector3d yAxis = new Vector3d(0, size, 0);
112
113 if (diff.X > 0 && diff.Y > 0) // Dragging top-right
114 orig = _start;
115 else if (diff.X < 0 && diff.Y > 0) // Top-left
116 orig = _start + new Vector3d(-size, 0, 0);
117 else if (diff.X > 0 && diff.Y < 0) // Bottom-right
118 orig = _start + new Vector3d(0, -size, 0);
119 else // if (diff.X < 0 && diff.Y < 0) // Bottom-left
120 orig = _start - new Vector3d(size, size, 0);
121
122 // Set the image's orientation in WCS
123
124 ri.Orientation =
125 new CoordinateSystem3d(
126 orig.TransformBy(_ucs),
127 xAxis.TransformBy(_ucs),
128 yAxis.TransformBy(_ucs)
129 );
130
131 return true;
132 }
133
134 public Entity GetEntity()
135 {
136 return Entity;
137 }
138 }
139
140 [CommandMethod("QR")]
141 static public void QRCode()
142 {
143 // Base record name and URL constants
144
145 const string recBase = "ADNP_QR";
146 const string rootUrl =
147 "http://chart.apis.google.com/chart?cht=qr&chs=500x500&chl=";
148
149 Document doc =
150 Application.DocumentManager.MdiActiveDocument;
151 Database db = doc.Database;
152 Editor ed = doc.Editor;
153
154 PromptResult pr =
155 ed.GetString("\nEnter email address to encode: ");
156 if (pr.Status != PromptStatus.OK)
157 return;
158
159 // Encode the colon and the @ symbol for the URL
160
161 string message =
162 "mailto%3A" + pr.StringResult.Replace("@", "%40");
163
164 Transaction tr =
165 doc.TransactionManager.StartTransaction();
166 using (tr)
167 {
168 ObjectId dictId =
169 RasterImageDef.GetImageDictionary(db);
170
171 if (dictId.IsNull)
172 {
173 // Image dictionary doesn't exist, create new
174
175 dictId =
176 RasterImageDef.CreateImageDictionary(db);
177 }
178
179 // Open the image dictionary
180
181 DBDictionary dict =
182 (DBDictionary)tr.GetObject(
183 dictId,
184 OpenMode.ForRead
185 );
186
187 // Get a unique record name for our raster image
188 // definition
189
190 int i = 0;
191 string recName = recBase + i.ToString();
192
193 while (dict.Contains(recName))
194 {
195 i++;
196 recName = recBase + i.ToString();
197 }
198
199 RasterImageDef rid = new RasterImageDef();
200
201 // Set its source image
202
203 rid.SourceFileName = rootUrl + message;
204
205 // Load it
206
207 rid.Load();
208 dict.UpgradeOpen();
209
210 ObjectId defId = dict.SetAt(recName, rid);
211
212 // Let the transaction know
213
214 tr.AddNewlyCreatedDBObject(rid, true);
215
216 PromptPointResult ppr =
217 ed.GetPoint("\nFirst corner of QR Code: ");
218 if (ppr.Status != PromptStatus.OK)
219 return;
220
221 // Call our jig to define the raster
222
223 SquareRasterJig jig =
224 new SquareRasterJig(
225 defId,
226 ed.CurrentUserCoordinateSystem,
227 ppr.Value
228 );
229 PromptResult prj = ed.Drag(jig);
230
231 if (prj.Status != PromptStatus.OK)
232 {
233 rid.Erase();
234 return;
235 }
236
237 // Get our entity and add it to the modelspace
238
239 RasterImage ri = (RasterImage)jig.GetEntity();
240
241 BlockTable bt =
242 (BlockTable)tr.GetObject(
243 db.BlockTableId,
244 OpenMode.ForRead
245 );
246
247 BlockTableRecord btr =
248 (BlockTableRecord)tr.GetObject(
249 bt[BlockTableRecord.ModelSpace],
250 OpenMode.ForWrite
251 );
252
253 btr.AppendEntity(ri);
254 tr.AddNewlyCreatedDBObject(ri, true);
255
256 // Create a reactor between the RasterImage and the
257 // RasterImageDef to avoid the "unreferenced"
258 // warning in the XRef palette
259
260 RasterImage.EnableReactors(true);
261 ri.AssociateRasterDef(rid);
262
263 // Let's add our message string as XData,
264 // in case we need it later
265
266 AddRegAppTableRecord("ADNP_QR");
267 ResultBuffer rb =
268 new ResultBuffer(
269 new TypedValue(1001, "ADNP_QR"),
270 new TypedValue(1000, message)
271 );
272 ri.XData = rb;
273 rb.Dispose();
274
275 tr.Commit();
276 }
277 }
278
279 static void AddRegAppTableRecord(string regAppName)
280 {
281 Document doc =
282 Application.DocumentManager.MdiActiveDocument;
283 Editor ed = doc.Editor;
284 Database db = doc.Database;
285
286 Transaction tr =
287 doc.TransactionManager.StartTransaction();
288 using (tr)
289 {
290 RegAppTable rat =
291 (RegAppTable)tr.GetObject(
292 db.RegAppTableId,
293 OpenMode.ForRead,
294 false
295 );
296 if (!rat.Has(regAppName))
297 {
298 rat.UpgradeOpen();
299 RegAppTableRecord ratr =
300 new RegAppTableRecord();
301 ratr.Name = regAppName;
302 rat.Add(ratr);
303 tr.AddNewlyCreatedDBObject(ratr, true);
304 }
305 tr.Commit();
306 }
307 }
308 }
309 }
The changes themselves should be reasonably clear: the code defining the size of the boundary has been moved to the jig, and we’re now using the jig to manage the user input.
Here's what happens when we use our updated QR command to jig the position and size of a QR Code:
I haven’t implemented rotation, as it’s not clear to me that it needs to be an integrated part of the QR command. If it’s only needed rarely – my current expectation – it’s probably better to leave it to an existing command such as PROPERTIES or ROTATE.
The next stage in this application’s development is to take a look at some kind of UI for defining different sets of content, as well as the ability to edit the contents of an existing QR Code.