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  










« RockScroll: my new favourite Visual Studio Add-In | Main | It was 15 years ago today »

August 27, 2010

Jigging an upright, square, AutoCAD raster image using .NET

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:

Jigging our square, QR Code raster image

Our jigged QR Code raster image 

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.

blog comments powered by Disqus

Feed/Share

10 Random Posts