I feel like I’m posting a lot about our Plugin of the Month initiative, at the moment, but then I suppose it’s to some degree a function of what I’m spending time on. It’s definitely eating into the time I would normally focus on blogging… after a number of AU-related posts coming out over the next few weeks (as I finish my AU prep next week and then blog from the event the week after) I’d hope to return to somewhat more typical posts in December (although as I’m going on to Japan, Korea and China from Las Vegas, this might be a bit optimistic).
Anyway, I thought it was a good time to unveil December’s plugin, a tool donated by our old friend Terry Dotson. Terry’s company, DotSoft, has been a member of the Autodesk Developer Network since 1992 (since before it was even called ADN, in fact :-). I’ve spent a little time changing variable names and reformatting the code to be consistent with this blog, but the techniques shown are very much Terry’s.
I really like this tool: it makes heavy use of the WinForms web browser control (System.Windows.Forms.WebBrowser) to display a report generated from the contents of the active drawing’s layer table. The application generates HTML based on the layers in the drawing and then saves it to a temporary folder before loading it into the browser control. The original implementation just set the DocumentText of the browser, but we found this limited the formats the control could save to (more on this feature in a moment). Once the report is displayed the user can customize the font and font-size as well as the choice of which columns to include in the report. These settings get saved by the application, so if you wish to generate reports for multiple drawings there’s not need to make the changes each time. Once the report looks right you can save it to file – whether to a web archive (.MHT), complete or simple web pages (.HTM) or to a plain text file (.TXT) – or print it.
Here’s an example of the results of the REPORTLAYERS command on a blank drawing:
Now let’s run it on a more complex drawing with more layers, and adjust the settings to suit our particular needs:
We can save the report…
We can configure its page setup…
Preview the print session…
Or just go ahead and print it directly…
Now for the code. The application was written in VB.NET (which is great, as it gives us – so far – a monthly alternation between C# and VB). You can download a preview version of the tool with source code here.
Here’s the main “code behind” the Report class, which does the bulk of the work:
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Colors
Imports System.Text
Imports System.Drawing
Public Class Report
Private _init As Boolean
Private _bmps As New Dictionary(Of String, String)
Private _tmpHtm As String = ""
Private Const kApp = "ADNPlugin-LayerReporter"
Private Const kSec = "GenerateReport"
Private Const kFontName = "FontName"
Private Const kFontSize = "FontSize"
Private Const kRetainBmps = "RetainBitmaps"
Private Const kFieldFlags = "FieldFlags"
Private Sub AppReport_FormClosing( _
ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles Me.FormClosing
Dim flags As Int32 = 0
If statusFld.Checked Then flags += 1
If onFld.Checked Then flags += 2
If freezeFld.Checked Then flags += 4
If lockFld.Checked Then flags += 8
If colorFld.Checked Then flags += 16
If ltypeFld.Checked Then flags += 32
If lweightFld.Checked Then flags += 64
If pstyleFld.Checked Then flags += 128
If plotFld.Checked Then flags += 256
If descFld.Checked Then flags += 512
If xrefsFld.Checked Then flags += 1024
SaveSetting(kApp, kSec, kFontName, fontName.Text)
SaveSetting(kApp, kSec, kFontSize, fontSize.Text)
SaveSetting(kApp, kSec, kRetainBmps, bmpsFld.Checked)
SaveSetting(kApp, kSec, kFieldFlags, flags)
' Clean up temporary bitmaps?
If Not bmpsFld.Checked Then
For Each kv As KeyValuePair(Of String, String) In _bmps
If File.Exists(kv.Value) Then
File.Delete(kv.Value)
End If
Next
End If
If _tmpHtm <> "" And File.Exists(_tmpHtm) Then
File.Delete(_tmpHtm)
End If
End Sub
Private Sub AppReport_Load( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
_init = True
' Export bitmaps to Temp folder and store in dictionary.
' This lets us easily refer to the bitmap file by key name
' later and get the files when it's time to clean up.
Dim tmpLoc As String = Path.GetTempPath
Dim bmps() As String = _
{"frz", "loc", "nop", "off", "onn", "plt", _
"thw", "unl", "use", "unu"}
For Each key As String In bmps
Dim resname As String = "lay" & key
Dim fname As String = tmpLoc & resname & ".bmp"
_bmps.Add(key, fname)
Using tmpBmp As Bitmap = _
My.Resources.ResourceManager.GetObject(resname)
tmpBmp.Save(fname)
End Using
Next
' Put font names in popdown list
For Each fntFam As FontFamily In FontFamily.Families
fontName.Items.Add(fntFam.Name)
Next
fontName.Text = _
GetSetting(kApp, kSec, kFontName, "Arial")
fontSize.Text = _
GetSetting(kApp, kSec, kFontSize, "medium")
bmpsFld.Checked = _
GetSetting(kApp, kSec, kRetainBmps, False)
Dim flags As Int32 = _
GetSetting(kApp, kSec, kFieldFlags, 1023)
statusFld.Checked = flags And 1
onFld.Checked = flags And 2
freezeFld.Checked = flags And 4
lockFld.Checked = flags And 8
colorFld.Checked = flags And 16
ltypeFld.Checked = flags And 32
lweightFld.Checked = flags And 64
pstyleFld.Checked = flags And 128
plotFld.Checked = flags And 256
descFld.Checked = flags And 512
xrefsFld.Checked = flags And 1024
Call GenerateReport()
_init = False
End Sub
' The SelectedIndexChanged event fires when the user changes
' a value in the related popdown menu at the top of the form.
Private Sub fontName_SelectedIndexChanged( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles fontName.SelectedIndexChanged
If Not _init Then
Call GenerateReport()
End If
End Sub
Private Sub fontSize_SelectedIndexChanged( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles fontSize.SelectedIndexChanged
If Not _init Then
Call GenerateReport()
End If
End Sub
' Shared Sub handles all Options toggles to regenerate
' the report, note that each click event is included and
' separated by commas.
Private Sub MenuReact( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles statusFld.Click, onFld.Click, freezeFld.Click, _
lockFld.Click, colorFld.Click, ltypeFld.Click, _
lweightFld.Click, pstyleFld.Click, plotFld.Click, _
descFld.Click, xrefsFld.Click
Call GenerateReport()
End Sub
Private Sub GenerateReport()
' Dimension some variables as placeholders for repeatedly
' used strings for smaller more readable code below.
Dim dwg As String = _
Application.GetSystemVariable("DWGPREFIX") & _
Application.GetSystemVariable("DWGNAME")
Dim stylePre As String = _
"style='font-family: " & fontName.Text & "; font-size: " & _
fontSize.Text
Dim headPre As String = "<th>" & " "
Dim headSuf As String = " " & "</th>"
Dim imgPre As String = _
"<td align='center'> <img src='"
Dim imgSuf As String = "'/> </td>"
Dim txtPre As String = "<td>" & " "
Dim txtSuf As String = " " & "</td>"
' Get an instance of the database and layer table and create
' a transaction by using 'Using' instead of 'Dim' the instance
' will be automatically disposed after execution is complete.
Using db As Database = _
Application.DocumentManager.MdiActiveDocument.Database
Using tr As Transaction = _
db.TransactionManager.StartTransaction
Using lt As LayerTable = _
db.LayerTableId.GetObject(OpenMode.ForRead)
' Loop through the layer table and build a list of
' qualifying layer names so we can sort it for output.
' The default order is likely the order created.
Dim lays As New List(Of String)
For Each layId As ObjectId In lt
Dim ltr As LayerTableRecord = _
tr.GetObject(layId, OpenMode.ForRead)
If xrefsFld.Checked Then
lays.Add(ltr.Name)
Else
If Not ltr.IsDependent Then
lays.Add(ltr.Name)
End If
End If
Next
lays.Sort()
' The System.Text.StringBuilder is an extremely fast way
' of concatenating strings, see the .NET help for more
' details on this versatile object!
Dim sb As New StringBuilder()
sb.Append( _
"<html><head>" & _
"<title>" & dwg & "</title>" & _
"</head>" & _
"<body>")
sb.Append("<center><div " & stylePre & "'>")
sb.Append( _
"<b>" & "Layer Report" & _
"</b>" & "<br/>")
sb.Append( _
"<b>" & dwg & "</b>" & "<br/>")
sb.Append( _
Format(Now, "Long Date") & " " & _
Format(Now, "Long Time") & "<br/>")
sb.Append( _
"<table " & stylePre & ";border-collapse: collapse; " & _
"border='0' " & _
"cellpadding='0'>")
sb.Append("<tr>")
If statusFld.Checked Then
sb.Append(headPre & "Status" & headSuf)
End If
sb.Append(headPre & "Name" & headSuf)
If onFld.Checked Then
sb.Append(headPre & "On" & headSuf)
End If
If freezeFld.Checked Then
sb.Append(headPre & "Freeze" & headSuf)
End If
If lockFld.Checked Then
sb.Append(headPre & "Lock" & headSuf)
End If
If colorFld.Checked Then
sb.Append(headPre & "Color" & headSuf)
End If
If ltypeFld.Checked Then
sb.Append(headPre & "Linetype" & headSuf)
End If
If lweightFld.Checked Then
sb.Append(headPre & "Lineweight" & headSuf)
End If
If pstyleFld.Checked Then
sb.Append(headPre & "PlotStyle" & headSuf)
End If
If plotFld.Checked Then
sb.Append(headPre & "Plot" & headSuf)
End If
If descFld.Checked Then
sb.Append(headPre & "Description" & headSuf)
End If
sb.Append("</tr>")
sb.Append("<tr><td> </td></tr>")
' The title and header section is done, time to fill in
' the layer rows.
For Each lay As String In lays
' Get the layer record by its key name string using the
' transactions.GetObject()
Dim ltr As LayerTableRecord = _
tr.GetObject(lt(lay), OpenMode.ForRead)
sb.Append("<tr>")
If statusFld.Checked Then
sb.Append( _
imgPre & _
IIf(ltr.IsUsed, _bmps("use"), _bmps("unu")) & _
imgSuf)
End If
sb.Append(txtPre & ltr.Name & txtSuf)
If onFld.Checked Then
sb.Append( _
imgPre & _
IIf(ltr.IsOff, _bmps("off"), _bmps("onn")) & _
imgSuf)
End If
If freezeFld.Checked Then
sb.Append( _
imgPre & _
IIf(ltr.IsFrozen, _bmps("frz"), _bmps("thw")) & _
imgSuf)
End If
If lockFld.Checked Then
sb.Append( _
imgPre & _
IIf(ltr.IsLocked, _bmps("loc"), _bmps("unl")) & _
imgSuf)
End If
If colorFld.Checked Then
' Used so we can show the square bullet in the
' layers color
sb.Append( _
"<td> <span style='font-family: Wingdings; " & _
"color: " & Col2Str(ltr.Color) & "'>n</span>")
sb.Append( _
" " & _
ltr.Color.ColorNameForDisplay & _
" " & "</td>")
End If
If ltypeFld.Checked Then
' The layer record stores the linetype as an ObjectId,
' not a string. This means we need to look up the
' linetype in that table to get it's string name.
Dim ltt As LinetypeTable = _
db.LinetypeTableId.GetObject(OpenMode.ForRead)
Dim lttr As LinetypeTableRecord = _
tr.GetObject(ltr.LinetypeObjectId, OpenMode.ForRead)
sb.Append(txtPre & lttr.Name & txtSuf)
End If
If lweightFld.Checked Then
sb.Append( _
txtPre & _
Wght2Str(ltr.LineWeight) & _
txtSuf)
End If
If pstyleFld.Checked Then
' The plot style is stored as a string, we can simply
' include it
sb.Append(txtPre & ltr.PlotStyleName & txtSuf)
End If
If plotFld.Checked Then
sb.Append( _
imgPre & _
IIf(ltr.IsPlottable, _bmps("plt"), _bmps("nop")) _
& imgSuf)
End If
If descFld.Checked Then
sb.Append(txtPre & ltr.Description & txtSuf)
End If
sb.Append("</tr>")
Next
sb.Append("</table></div></center></body></html>")
' Write the HTML content to a file and then load it
If _tmpHtm = "" Then
_tmpHtm = Path.GetTempFileName() + ".htm"
End If
Using file As StreamWriter = _
New StreamWriter(_tmpHtm, False, Encoding.Default)
file.Write(sb.ToString)
file.Close()
End Using
webWin.SuspendLayout()
webWin.Navigate(_tmpHtm)
' Wait for the control to finish
While webWin.IsBusy
System.Windows.Forms.Application.DoEvents()
End While
webWin.ResumeLayout()
statTxt.Text = _
lays.Count.ToString & " layer" & _
IIf(lays.Count = 1, "", "s") & " listed"
End Using
End Using
End Using
End Sub
Private Sub printBut_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles printBut.Click
webWin.ShowPrintDialog()
End Sub
Private Sub prevBut_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles prevBut.Click
webWin.ShowPrintPreviewDialog()
End Sub
Private Sub saveBut_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles saveBut.Click
webWin.ShowSaveAsDialog()
End Sub
Private Sub setupBut_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles setupBut.Click
webWin.ShowPageSetupDialog()
End Sub
' Helper function for the square bullet displayed, takes an _
' AutoCAD color as input as returns a string like #00FF00.
Private Function Col2Str( _
ByVal c As Autodesk.AutoCAD.Colors.Color) As String
Dim col As Int32
If c.ColorMethod = ColorMethod.ByAci Then
Dim bytes() As Byte = _
BitConverter.GetBytes( _
EntityColor.LookUpRgb(c.ColorIndex))
col = RGB(bytes(0), bytes(1), bytes(2))
Else
col = RGB(c.Blue, c.Green, c.Red)
End If
Return ColorTranslator.ToHtml( _
System.Drawing.Color.FromArgb(col))
End Function
' Helper function for lineweights, takes the stored value
' and returns a string similar to AutoCAD's layer dialog
' (in the current units).
Private Function Wght2Str(ByVal weight As Single) _
As String
Select Case weight
Case -3
Return "Default"
Case -2
Return "ByBlock"
Case -1
Return "ByLayer"
Case Else
If Application.GetSystemVariable("LWUNITS") = 1 Then
Return Format(weight / 100.0, "0.00") & "mm"
Else
Return Format(weight / 25.4 / 100.0, "0.000") & Chr(34)
End If
End Select
End Function
End Class
There’s an additional Commands.vb file:
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Runtime
Imports LayerReporter.DemandLoading
Public Class Commands
Implements IExtensionApplication
' AutoCAD fires the Initialize Sub when the assembly is loaded,
' so we check to see if the command is registered by looking for
' the existance of the Registry keys. If not found, we attempt
' to add them so the command will automatically load in future
' sessions. This means it only needs to be NETLOADed once.
Public Sub Initialize() Implements IExtensionApplication.Initialize
RegistryUpdate.RegisterForDemandLoading()
End Sub
Public Sub Terminate() Implements IExtensionApplication.Terminate
' Only fires when AutoCAD is shutting down
End Sub
<CommandMethod("ADNPLUGINS", "REMOVELR", CommandFlags.Modal)> _
Public Shared Sub RemoveLayerReporter()
RegistryUpdate.UnregisterForDemandLoading()
End Sub
' Establish our command name to use to start the tool. It
' dimensions an instance of the form and displays it with
' AutoCAD's ShowModalDialog. Don't use Windows Form.Show!
<CommandMethod("ADNPLUGINS", "REPORTLAYERS", CommandFlags.Modal)> _
Public Shared Sub ReportLayers()
Using dlg As New Report()
Application.ShowModalDialog(dlg)
End Using
End Sub
End Class
And the Demand_Loading.vb file is the same as was used in the ClipboardManager tool.
If you have any feedback to share please post a comment or send us an email.