Taking a snapshot of the AutoCAD model (take 2)

In this previous post, we looked at some code to do a programmatic snapshot of AutoCAD's modelspace, saving the results to an image file.

From the discussion that followed, I realised that the code had an undesired (and unnecessary) side-effect of creating a new 3D GS View and leaving the modelspace with that view active. GS Views in AutoCAD 2007 have grey backgrounds by default, and so this change can be quite disturbing for users. The only reason we created the GS View in the first place (if one didn't already exist), was to use it to query the view position/target/up vector/field width and height and apply it to our new view. Thankfully it seems this can also be determined directly from the viewport.

So rather than calling GetGSView() and using the returned view to get that information, we now simply call SetViewFromViewport() specifying the viewport number held in CVPORT, and the graphics system manager for that document handles the rest.

Here's the updated C# code, which appears to achieve the same goals without the side-effect. Check line 124 for the new code, a few extraneous lines around it having been removed:

    1 using Autodesk.AutoCAD.ApplicationServices;

    2 using Autodesk.AutoCAD.DatabaseServices;

    3 using Autodesk.AutoCAD.EditorInput;

    4 using Autodesk.AutoCAD.GraphicsInterface;

    5 using Autodesk.AutoCAD.GraphicsSystem;

    6 using Autodesk.AutoCAD.Runtime;

    7 using Autodesk.AutoCAD.Interop;

    8 using System.Drawing;

    9

   10 namespace OffscreenImageCreation

   11 {

   12   public class Commands

   13   {

   14     [CommandMethod("OSS")]

   15     static public void OffscreenSnapshot()

   16     {

   17       CreateSphere();

   18       SnapshotToFile(

   19         "c:\\sphere-Wireframe2D.png",

   20         VisualStyleType.Wireframe2D

   21       );

   22       SnapshotToFile(

   23         "c:\\sphere-Hidden.png",

   24         VisualStyleType.Hidden

   25       );

   26       SnapshotToFile(

   27         "c:\\sphere-Basic.png",

   28         VisualStyleType.Basic

   29       );

   30       SnapshotToFile(

   31         "c:\\sphere-ColorChange.png",

   32         VisualStyleType.ColorChange

   33       );

   34       SnapshotToFile(

   35         "c:\\sphere-Conceptual.png",

   36         VisualStyleType.Conceptual

   37       );

   38       SnapshotToFile(

   39         "c:\\sphere-Flat.png",

   40         VisualStyleType.Flat

   41       );

   42       SnapshotToFile(

   43         "c:\\sphere-Gouraud.png",

   44         VisualStyleType.Gouraud

   45       );

   46       SnapshotToFile(

   47         "c:\\sphere-Realistic.png",

   48         VisualStyleType.Realistic

   49       );

   50     }

   51

   52     static public void CreateSphere()

   53     {

   54       Document doc =

   55         Application.DocumentManager.MdiActiveDocument;

   56       Database db = doc.Database;

   57       Editor ed = doc.Editor;

   58

   59       Transaction tr =

   60         doc.TransactionManager.StartTransaction();

   61       using (tr)

   62       {

   63         BlockTable bt =

   64           (BlockTable)tr.GetObject(

   65             db.BlockTableId,

   66             OpenMode.ForRead

   67           );

   68         BlockTableRecord btr =

   69           (BlockTableRecord)tr.GetObject(

   70             bt[BlockTableRecord.ModelSpace],

   71             OpenMode.ForWrite

   72           );

   73         Solid3d sol = new Solid3d();

   74         sol.CreateSphere(10.0);

   75

   76         const string matname =

   77           "Sitework.Paving - Surfacing.Riverstone.Mortared";

   78         DBDictionary matdict =

   79           (DBDictionary)tr.GetObject(

   80             db.MaterialDictionaryId,

   81             OpenMode.ForRead

   82           );

   83         if (matdict.Contains(matname))

   84         {

   85           sol.Material = matname;

   86         }

   87         else

   88         {

   89           ed.WriteMessage(

   90             "\nMaterial (" + matname + ") not found" +

   91             " - sphere will be rendered without it.",

   92             matname

   93           );

   94         }

   95         btr.AppendEntity(sol);

   96

   97         tr.AddNewlyCreatedDBObject(sol, true);

   98         tr.Commit();

   99       }

  100       AcadApplication acadApp =

  101         (AcadApplication)Application.AcadApplication;

  102       acadApp.ZoomExtents();

  103     }

  104

  105     static public void SnapshotToFile(

  106       string filename,

  107       VisualStyleType vst

  108     )

  109     {

  110       Document doc =

  111         Application.DocumentManager.MdiActiveDocument;

  112       Editor ed = doc.Editor;

  113       Database db = doc.Database;

  114       Manager gsm = doc.GraphicsManager;

  115

  116       // Get some AutoCAD system variables

  117       int vpn =

  118         System.Convert.ToInt32(

  119           Application.GetSystemVariable("CVPORT")

  120         );

  121

  122       using (View view = new View())

  123       {

  124         gsm.SetViewFromViewport(view, vpn);

  125

  126         // Set the visual style to the one passed in

  127         view.VisualStyle = new VisualStyle(vst);

  128

  129         Device dev =

  130           gsm.CreateAutoCADOffScreenDevice();

  131         using (dev)

  132         {

  133           dev.OnSize(gsm.DisplaySize);

  134

  135           // Set the render type and the background color

  136           dev.DeviceRenderType = RendererType.Default;

  137           dev.BackgroundColor = Color.White;

  138

  139           // Add the view to the device and update it

  140           dev.Add(view);

  141           dev.Update();

  142

  143           using (Model model = gsm.CreateAutoCADModel())

  144           {

  145             Transaction tr =

  146               db.TransactionManager.StartTransaction();

  147             using (tr)

  148             {

  149               // Add the modelspace to the view

  150               // It's a container but also a drawable

  151               BlockTable bt =

  152                 (BlockTable)tr.GetObject(

  153                   db.BlockTableId,

  154                   OpenMode.ForRead

  155                 );

  156               BlockTableRecord btr =

  157                 (BlockTableRecord)tr.GetObject(

  158                   bt[BlockTableRecord.ModelSpace],

  159                   OpenMode.ForRead

  160                 );

  161               view.Add(btr, model);

  162               tr.Commit();

  163             }

  164             // Take the snapshot

  165             Rectangle rect = view.Viewport;

  166             using (Bitmap bitmap = view.GetSnapshot(rect))

  167             {

  168               bitmap.Save(filename);

  169               ed.WriteMessage(

  170                 "\nSnapshot image saved to: " +

  171                 filename

  172               );

  173               // Clean up

  174               view.EraseAll();

  175               dev.Erase(view);

  176             }

  177           }

  178         }

  179       }

  180     }

  181   }

  182 }

You can also download the .cs file from here.

  1. Kean,

    Great post... but i'm having a few issues trying to get a snap shot of a drawing that is not the current one and not open inside AutoCAD, i.e. trying to get similar functionality to the BlockView sample in the ARX docs, but using .net

    i guess the question is, is it possible to get a snapshot from only the database?

    i'm using the following code (which is mostly untouch from the example above, except for the database) but all i get is a black bitmap

    [code]
    Public Shared Sub SnapshotToFile(ByVal filename As String, ByVal vst As VisualStyleType)

    Dim NewDatabase As Autodesk.AutoCAD.DatabaseServices.Database = New Autodesk.AutoCAD.DatabaseServices.Database(False, True)
    NewDatabase.ReadDwgFile("D:\Test\Common.dwg", IO.FileShare.Read, True, Nothing)

    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    'Dim ed As Editor = doc.Editor
    Dim db As Database = NewDatabase
    Dim gsm As Manager = doc.GraphicsManager

    ' Get some AutoCAD system variables
    Dim vpn As Integer = 2 ' System.Convert.ToInt32(Application.GetSystemVariable("CVPORT"))

    Using view As New View()
    gsm.SetViewFromViewport(view, vpn)

    ' Set the visual style to the one passed in
    view.VisualStyle = New VisualStyle(vst)

    Dim dev As Device = gsm.CreateAutoCADOffScreenDevice()
    Using dev
    dev.OnSize(gsm.DisplaySize)

    ' Set the render type and the background color
    dev.DeviceRenderType = RendererType.[Default]
    dev.BackgroundColor = Color.Black ' Color.White

    ' Add the view to the device and update it
    dev.Add(view)
    dev.Update()

    Using model As Model = gsm.CreateAutoCADModel()
    Dim tr As Transaction = db.TransactionManager.StartTransaction()
    Using tr
    ' Add the modelspace to the view
    ' It's a container but also a drawable
    Dim bt As BlockTable = DirectCast(tr.GetObject(db.BlockTableId, OpenMode.ForRead), BlockTable)
    Dim btr As BlockTableRecord = DirectCast(tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForRead), BlockTableRecord)
    view.Add(btr, model)
    tr.Commit()
    End Using
    ' Take the snapshot
    Dim rect As Rectangle = view.Viewport
    Using bitmap As Bitmap = view.GetSnapshot(rect)
    bitmap.Save(filename)
    'ed.WriteMessage("" & Chr(10) & "Snapshot image saved to: " + filename)
    ' Clean up
    view.EraseAll()
    dev.[Erase](view)
    End Using
    End Using
    End Using
    End Using
    End Sub
    [code]

    any help would be great

    Cheers

    Mark

  2. Hi Mark,

    I'm pretty sure you need the document open to be able to make use of the graphics system in this way: it needn't be the view shown in the editor, but I do think the document needs to be loaded.

    Regards,

    Kean

  3. Thanks for the post.

    Would you know how to get what the coordinates of a Point2d would be inside the bitmap (in pixels)?

    Thanks.

  4. Answering my own question... it seems like I simply need to transform the point using the matrix view.WorldToDeviceMatrix

  5. On some machine running AutoCAD 2008, this code fails dramatically with a memory corrupt error. It works fine on another machine running AutoCAD 2008, so I am very confused as to why this error happens. I am using ObjectARX 2007 -- could it be failing because of some confusion between 2007 and 2008 dlls? I never had an issue using ObjectARX 2007 with AutoCAD 2008 before.

  6. Please email me precise steps to reproduce the problem. It shouldn't be related to using the 2007 ObjectARX libs with AutoCAD 2008, but then you never know.

    Kean

  7. Thanks, Kean, I emailed you the error. Just to be more precise, the point of failure is this line:
    using (Bitmap bitmap = view.GetSnapshot(view.Viewport))

  8. More, specifically, the error is here:
    System.Runtime.InteropServices.ExternalException was unhandled by user code
    Message="A generic error occurred in GDI+."
    Source="System.Drawing"
    ErrorCode=-2147467259
    StackTrace:
    at System.Drawing.Image.FromHbitmap(IntPtr hbitmap, IntPtr hpalette)
    at System.Drawing.Image.FromHbitmap(IntPtr hbitmap)
    at Autodesk.AutoCAD.GraphicsSystem.View.GetSnapshot(Rectangle rectangle)
    at ... SnapshotToFile(String filename, VisualStyleType vst) in ...

    I even updated the .NET framework to 3.5 to no avail.

  9. Hi Kean,

    Thanks for the great post. I have this working, but I have a significant problem:

    The drawings I'm trying to export as images are very simple, lines only (ok, some letters too) technical drawings. The export does happen, but the image quality, well, sucks. To the point that a circle is more like an octagon.

    I've tried some tricks, namely bitmap.setresolution and changing the view.VisualStyle but nothing worked.

    Any ideas ?

  10. @namin :

    I've come across this too. It's frustrating because the message doesn't reveal what the actual problem is.

    Usually it's a problem writing the file to the disk; e.g. permissions problem, the folder does not exist etc.

  11. Dimitris -

    There's no particular reason the quality should be poor: I'm sure it's just a matter of making the right settings.

    I suggest you submit it via ADN, if you're a member, or otherwise try the AutoCAD .NET Discussion Group. At a very last resort, email a DWG to me, but I'm afraid I can't predict (or commit to) when I'll get the time to look into it.

    Kean

  12. @namin:

    Hi Namin, you can try changing the resolution of colors of your desktop. Simply, the 'GetSnapshot' instruction perhaps export a bitmap with a 32 bit resolution (view resolution) and your desktop may be 16 bit resolution.

  13. Hi Kean,

    I tried to develop a routine based on this post but I found 2 things that I'd like to solve if possible.
    Your routine is just not usable for larger drawings (takes way to long). Also, your routine does not work properly in paper space.

    Please, don't get me wrong, I appreciate all the work you've put into this blog. I just desperately need to come up with a solution for snapshots that work on large drawings, that work in paper/model space and that use the current viewstyle settings (except the background color).

    Isn't there a way to somehow mimic the GetGsView function that is fast and works in paper space as well but has a downfall of creating a 3D view in the actual drawing whenever a 2D Wireframe visual style is set?

    Any help is really appreciated.

  14. Hi Krayzie,

    For your specific needs you're probably better off just using the display pixels to capture a screenshot of the application frame, using a technique such as this:

    http://www.geekpedia.com/tutorial181_Capturing-screenshots-using-Csharp.html

    Regards,

    Kean

  15. Thanx for such a quick reply. I am not sure whether taking whole screenshots is a way to go (some parts might be covered by dialogue boxes), I'll give it a try though.

    I used take the snapshots with the standard HBITMAP and document HWND technique, however once the HW acceleration in AutoCAD is turned on, it always captures a blank screen.

    Anyway, I am still struggling with the pure .NET approach. Basically, the GetGsView(#, true)->GetSnapshot is almost perfect for whatever I need. If only I could somehow reset the display after taking the snapshot, otherwise, it is not usable. A simple manual switch between MODEL and LAYOUT tab (and back) does it all, I just cannot find a way to refresh the view programmatically (tried Regen, UpdateScreen, Update, Invalidate, ..., but nothing does the trick). Any other ideas?

  16. Try the code in this post, and see whether it works for you...

    Kean

  17. Hi Kean,

    unfortunately the standard .NET screenshot technique is a no go for me because of possible dialogue boxes or controls in front of the document window. Thanks for the effort though.
    Anyway, I cannot spend more time with it. For now, I solved it by exporting the view using the PNGOUT command into a temporary file. So far, it seems to work quite nicely as this command does everything I need. If only I could mimic the PNGOUT command programmatically to avoid possible unexpected behavior while scripting commands, but nevermind.

    Thanks again for trying.

    Regards,
    Krayzie

  18. Hi Kean,

    I did try to re-use part of your code to simply change the visual style of my current working view but it seems that it's not that easy.

    Here is the code I used :

    view = gsm.GetGsView(GetCurrentViewportId(), true);
    view.VisualStyle = new VisualStyle(VisualStyleType.Realistic);
    gsm.SetViewportFromView(GetCurrentViewportId(), view, true, true, false);

    And it just does nothing.
    Any idea or document I could read that could help me go the right way ?

    Regards,

  19. Hi Alexandre,

    Sorry - I've scripted the VISUALSTYLE command to do this, in the past, but haven't tried to do it at a lower API level.

    I suggest submitting your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Regards,

    Kean

Leave a Reply to namin Cancel reply

Your email address will not be published. Required fields are marked *