There was an interesting coincidence, earlier in the week. A blog comment came in on an old post on the exact day there was an internal email discussion on the same topic.
The post was about using the BLOCKICON command on a document that gets loaded into the editor just for that purpose, generating a thumbnail image on disk for each of the contained block definitions. The problem with this approach – as was suggested by a couple of people in blog comments – is that the generated thumbnail is really small at just 32 x 32 pixels.
The exact same question – and concern – came up on an internal email discussion and the following solution was suggested by Kun Qian from our Shanghai office: to use CMLContentSearchPreviews.GetBlockTRThumbnail(BlockTableRecord blockTR) to generate them, instead.
I was instantly intrigued, as I hadn’t come across this function before (and thought it might be flagged as internal-only), but – sure enough – it’s part of the public .NET API, in the Autodesk.AutoCAD.Windows.Data namespace.
Here’s an adapted version of the C# code in the older post that allows us to compare and contrast the two techniques:
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.Data;
namespace BlockPreviews
{
public class Commands
{
[CommandMethod("GBP", CommandFlags.Session)]
public static void GenerateBlockPreviews()
{
var ed =
Application.DocumentManager.MdiActiveDocument.Editor;
var res =
ed.GetFileNameForOpen(
"Select file for which to generate previews"
);
if (res.Status != PromptStatus.OK)
return;
string dwgname = res.StringResult;
Document doc = null;
try
{
doc =
Application.DocumentManager.Open(dwgname, false);
}
catch
{
ed.WriteMessage("\nUnable to read drawing.");
return;
}
var db = doc.Database;
string path = Path.GetDirectoryName(dwgname),
name = Path.GetFileName(dwgname),
iconPath = path + "\\" + name + " icons";
int numIcons = 0;
using (var tr = doc.TransactionManager.StartTransaction())
{
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
foreach (ObjectId btrId in bt)
{
var btr =
(BlockTableRecord)tr.GetObject(
btrId, OpenMode.ForRead
);
// Ignore layouts and anonymous blocks
if (btr.IsLayout || btr.IsAnonymous)
continue;
// Attempt to generate an icon, where one doesn't exist
if (btr.PreviewIcon == null)
{
object ActiveDocument = doc.GetAcadDocument();
object[] data = { "_.BLOCKICON " + btr.Name + "\n" };
ActiveDocument.GetType().InvokeMember(
"SendCommand",
System.Reflection.BindingFlags.InvokeMethod,
null, ActiveDocument, data
);
}
// Hopefully we now have an icon
if (btr.PreviewIcon != null)
{
// Create the output directory, if it isn't yet there
if (!Directory.Exists(iconPath))
Directory.CreateDirectory(iconPath);
var fname = iconPath + "\\" + btr.Name + ".bmp";
// Delete the image if it already exists
if (File.Exists(fname))
File.Delete(fname);
// Save the icon to our out directory
btr.PreviewIcon.Save(fname);
// Increment our icon counter
numIcons++;
}
}
tr.Commit();
}
doc.CloseAndDiscard();
ed.WriteMessage(
"\n{0} block icons saved to \"{1}\".", numIcons, iconPath
);
}
[CommandMethod("GBP2")]
public static void GenerateBlockPreviews2()
{
Editor ed =
Application.DocumentManager.MdiActiveDocument.Editor;
PromptFileNameResult res =
ed.GetFileNameForOpen(
"Select file for which to generate previews"
);
if (res.Status != PromptStatus.OK)
return;
string dwgname = res.StringResult;
int numIcons;
string iconPath;
// We don't need a document for access to the BlockTable
using (var db = new Database(false, true))
{
try
{
db.ReadDwgFile(
dwgname,
FileOpenMode.OpenForReadAndReadShare,
true,
""
);
}
catch
{
ed.WriteMessage("\nUnable to read drawing.");
return;
}
var path = Path.GetDirectoryName(dwgname);
var name = Path.GetFileName(dwgname);
iconPath = path + "\\" + name + " icons";
using (var tr = db.TransactionManager.StartTransaction())
{
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
numIcons = ExtractThumbnails(iconPath, tr, bt);
tr.Commit();
}
}
ed.WriteMessage(
"\n{0} block icons saved to \"{1}\".", numIcons, iconPath
);
}
[CommandMethod("GBPC")]
public static void GenerateCurrentBlockPreviews()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
var db = doc.Database;
var path = (string)Application.GetSystemVariable("DWGPREFIX");
var name = (string)Application.GetSystemVariable("DWGNAME");
var iconPath = path + name + " icons";
using (var tr = doc.TransactionManager.StartTransaction())
{
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
int numIcons = ExtractThumbnails(iconPath, tr, bt);
tr.Commit();
ed.WriteMessage(
"\n{0} block icons saved to \"{1}\".", numIcons, iconPath
);
}
}
private static int ExtractThumbnails(
string iconPath, Transaction tr, BlockTable bt
)
{
int numIcons = 0;
foreach (ObjectId btrId in bt)
{
var btr =
(BlockTableRecord)tr.GetObject(
btrId, OpenMode.ForRead
);
// Ignore layouts and anonymous blocks
if (btr.IsLayout || btr.IsAnonymous)
continue;
// Attempt to generate an icon, where one doesn't exist
{
// Create the output directory, if it isn't yet there
if (!Directory.Exists(iconPath))
Directory.CreateDirectory(iconPath);
// Save the icon to our out directory
var imgsrc =
CMLContentSearchPreviews.GetBlockTRThumbnail(btr);
var bmp =
ImageSourceToGDI(
imgsrc as System.Windows.Media.Imaging.BitmapSource
);
var fname = iconPath + "\\" + btr.Name + ".bmp";
if (File.Exists(fname))
File.Delete(fname);
bmp.Save(fname);
// Increment our icon counter
numIcons++;
}
}
return numIcons;
}
// Helper function to generate an Image from a BitmapSource
private static System.Drawing.Image ImageSourceToGDI(
System.Windows.Media.Imaging.BitmapSource src
)
{
var ms = new MemoryStream();
var encoder =
new System.Windows.Media.Imaging.BmpBitmapEncoder();
encoder.Frames.Add(
System.Windows.Media.Imaging.BitmapFrame.Create(src)
);
encoder.Save(ms);
ms.Flush();
return System.Drawing.Image.FromStream(ms);
}
}
}
The code implements a helper function that iterates through a block table and creates thumbnails for each block (I could certainly have decomposed the process further, creating a function to export a single block, for instance, but that’s left up to the reader).
Then there are two new commands: GBP2 and GBPC. GBP2 is an exact equivalent of the GBP command, but it uses the static GetBlockTRThumbnail() method – via our new helper function – to generate a thumbnail image for a particular BlockTableRecord. And this function doesn’t need a document to be open – unlike the GBP command – so it’s really much cleaner. GBPC calls the same helper but this time on the current drawing.
Here are the results of using the GBP command on one of the AutoCAD sample files. You can see the images are 32 x 32 pixels.
Here are the results of using GBP2 on the same file. Each of the images is now 190 x 120 pixels.
That’s really much better. Many thanks for the tip, Kun! :-)