AU Handouts: There's More to .DWG Than AutoCAD® - Part 1

[As mentioned in the last post, I'll be publishing chunks of the handouts for my AU classes via this blog over the coming weeks]

Introduction

This session focuses on downstream technologies that can be used to access DWG content outside of AutoCAD-based products. We’re going to start by creating a basic model inside AutoCAD comprised of 3D solid objects, and then look at a simple AutoCAD .NET application to access information about those solids. We will then take the same code and build a non-graphical (in terms of geometry - it does, after all, have a GUI) application around it using Autodesk RealDWG. Later on we’ll look at the same code running inside a custom-branded design application built using AutoCAD OEM.

We will also spend time looking at DWG TrueView and the DWG HOOPS viewer, to understand how they might be used to enhance the graphical display of DWGs without the editing overhead introduced with AutoCAD-based products.

Firstly, why are we using 3D solids in this example? The choice was somewhat arbitrary – the point is really to demonstrate the ability to access properties of objects stored in a DWG file without AutoCAD running – but it does suit our overall purpose for a few reasons:

  1. As 3D entities, 3D solids allow us to evaluate the 3D capabilities of the viewing technologies we’re looking at.
  2. They provide precise data that is of interest to us – in this case we’re going to mine their volume – and can also have data attached of a less precision-oriented nature, such as materials.
  3. Regarding both the above points, they also feed into the fact that this demo continues in another AU session, “DE401-2: Enriching Your DWF™”, which focuses on working with DWF data that is published from AutoCAD. In that session we’ll look at how to harness both 2D and 3D data to implement rich applications focused on published – rather than native – data.

A quick word on the programming technology used in this demonstration. The code samples are all in VB.NET: regular readers of my blog (http://blogs.autodesk.com/through-the-interface) will know I have a preference for C#, having spent years working with C++, but I’m sticking with VB.NET for this demonstration: we originally developed the material using VB.NET to reach the broader audience of Visual Basic professionals.

These handouts, along with the sample projects demonstrated within them, have been posted to my blog: http://blogs.autodesk.com/through-the-interface. Only significant portions of code will be highlighted in this document, and even those should not need to be typed back in. :-)

[You can download the source used in this demo from here for the AutoCAD application and here for the RealDWG application.]

Create a simple 3D model in AutoCAD and access it using .NET

There are various 3D modeling commands available in AutoCAD, of which detailed review is outside the scope of this session. We simply want to populate a model with a number of 3D solids with materials attached. Here’s the kind of thing we’re looking for:

3d_model

Figure 1 – a simple set of 3D solids inside AutoCAD

Now we’re ready to start looking at the code we’ll use inside an AutoCAD application, as well as within RealDWG and AutoCAD OEM, to access the information in this model.

Here’s code from a file called SolidInfo.vb to represent a data structure we’ll use to collect information about our 3D solids – their type, location, ID (the AutoCAD handle) and volume.

Imports Autodesk.AutoCAD.DatabaseServices

Imports Autodesk.AutoCAD.Geometry

Imports Autodesk.AutoCAD.Interop

Imports Autodesk.AutoCAD.Interop.Common

Imports System.Math


' The SolidData class encapsulates the data we

' want to collect and display for Solid3d objects


Public Class SolidData


  ' We care about Type, Location, ID & Volume


  Private m_Type As String

  Private m_Location As String

  Private m_Id As String

  Private m_Volume As Double


  Public Property Type()

    Get

      Return m_Type

    End Get

    Set(ByVal value)

      m_Type = value

    End Set

  End Property

  Public Property Location()

    Get

      Return m_Location

    End Get

    Set(ByVal value)

      m_Location = value

    End Set

  End Property

  Public Property Id()

    Get

      Return m_Id

    End Get

    Set(ByVal value)

      m_Id = value

    End Set

  End Property

  Public Property Volume()

    Get

      Return m_Volume

    End Get

    Set(ByVal value)

      m_Volume = value

    End Set

  End Property

End Class


' The SolidList class is simply a list of SolidData

' (used when defining the data a grid is bound to)


Public Class SolidList

  Inherits System.ComponentModel.BindingList(Of SolidData)

End Class

Now we have a function that will take a database and iterate through its contents, collecting information about 3D solid objects and adding them to a Data-Bound Grid passed in as the second argument. This grid should be created in the UI to be bound to objects of type SolidList (defined above).

Public Module SolidFunctions


  ' This function will get SolidData for each Solid3d

  ' in a Database and add them to a BindingSource (a grid

  ' in a form, typically). It also returns the total volume

  ' of Solid3ds in the Database


  Public Function AnalyzeDatabase _

    (ByVal db As Database, _

    ByRef sl As System.Windows.Forms.BindingSource) _

    As Double


    ' We will return the total volume of solids

    ' in the modelspace of this Database


    Dim vol As Double = 0

    sl.Clear()


    Dim tr As Transaction = _

      db.TransactionManager.StartTransaction()

    Using tr

      Try

        Dim bt As BlockTable = _

          tr.GetObject _

            (db.BlockTableId(), _

            OpenMode.ForRead)

        Dim btr As BlockTableRecord = _

          tr.GetObject _

            (bt(BlockTableRecord.ModelSpace), _

            OpenMode.ForRead)


        ' Loop for each entity in the modelspace


        For Each entId As ObjectId In btr

          Dim ent As Entity = _

            tr.GetObject(entId, OpenMode.ForRead)


          ' If it's a solid, then gather its data


          If TypeOf (ent) Is Solid3d Then


            Dim solid As Solid3d = CType(ent, Solid3d)

            Dim mp As Solid3dMassProperties = _

              solid.MassProperties()


            Dim sd As New SolidData


            ' Use the COM API to access the type string

            ' (exposed through neither ObjectARX nor .NET)


            Dim oSolid As Acad3DSolid

            oSolid = CType(solid.AcadObject, Acad3DSolid)

            sd.Type = oSolid.SolidType


            ' Use the centroid for the location


            sd.Location = _

              Round(mp.Centroid.X, 4).ToString() + ", " + _

              Round(mp.Centroid.Y, 4).ToString() + ", " + _

              Round(mp.Centroid.Y, 4).ToString()


            sd.Id = solid.Handle.ToString

            sd.Volume = Round(mp.Volume, 4)


            ' Add each solid's info to the list


            sl.Add(sd)


            ' And add its volume to the total


            vol += sd.Volume()


          End If

        Next


        ' Committing is cheaper than aborting,

        ' even if we didn't change the drawing


        tr.Commit()


      Catch ex As Exception

        MsgBox("Error: " + ex.Message)

      End Try

    End Using


    AnalyzeDatabase = Math.Round(vol, 2)


  End Function


End Module

We will now build the code into a complete AutoCAD .NET application which defines two commands – the first, SD, launches a dockable AutoCAD palette listing the 3D solids in our model and totaling their volume, and the second, SD2, does the same from a standard dialog:

3d_solids_palette_2

3d_solids_dialog

Figure 2 - the dialogs displayed by the SD and SD2 custom commands

These two dialogs share a common implementation: they both contain a UserControl that contains a DataGridView and a BindingNavigator to browse the contained records. There’s an optional “Analysis” button in the control, which allows the analysis operation to be launched manually. This is hidden when within the Dialog version, as it was primarily intended as a modal interface which launches the analysis automatically on load.

Build a RealDWG application from the AutoCAD .NET code

Now we will take the basic code from the SolidInfo.vb file and reuse it in a RealDWG application. It is also possible to use the User Control directly (defined in SolidUserControl.vb, SolidUserControl.Designer.vb and SolidUserControl.resx) but I thought it would be of more interest to demonstrate building the steps needed to build a RealDWG application and how it differs from a standard AutoCAD .NET module.

We start by creating a new Windows Application in Visual Studio 2005:

New_realdwg_project

Figure 3 – creating a new project for our RealDWG application

The first step is to copy across the SolidInfo.vb file into our project folder and add it into the project as an existing item. To get the project to build you will need to select two project references: one for the RealDWG managed assembly, and one for the COM type library:

Add_reference_to_realdwg_managed_as

Add_reference_to_com_type_library

Figure 4 - adding project references to the RealDWG managed assembly & COM type-library

We need the COM type library to work around a specific limitation in the .NET API to AutoCAD and in ObjectARX, its underlying API. These APIs do not provide access to the type of a solid – whether it’s a sphere, a cylinder or a pyramid, for instance – so we need to use COM to retrieve the information.

New VB.NET Windows Application projects contain a default form, Form1.vb. Rename this to MainForm.vb and design a simple user interface with a TextBox for a filename (named DwgFileToAnalyze), a Button to browse to the file (named BrowseButton) and a DataGridView (called whatever you like :-). When you come to adding the DataGridView you will be prompted to define a Data Source. Select “Add a New Data Source” of type “Object”. From here you will be able to browse down to our SolidList class:

Adding_the_datagridview

Adding_a_data_source

Figure 5 – adding a DataGridView and its associated Data Source

Now just add a Label (named TotalVolumeText) at the bottom (going the whole width of the dialog, setting the AutoSize property to “False”, the Font to something nice and visible and TextAlign to “MiddleRight”). You can leave the contents blank, as we’ll be setting those programmatically.

I always like the UI to behave well on resize, even with a sample app, so I’d suggest editing the Anchor property of the GroupBox and the TextBox to “Top, Left, Right”, of the DataGridView to “Top, Bottom, Left, Right”, and of the VolumeText to “Bottom, Left, Right”.

I’d also change the following DataGridView properties: AllowUserToAddRows to “False”, AllowUserTo DeleteRows to “False” and AutoSizeColumnsMode to “Fill”.

The User Interface is now done – we’re ready to flesh out the application functionality to call our AnalyzeDatabase() from SolidInfo.vb.

First we need to implement some code to make our RealDWG application valid. We’ll add a new file called Program.vb and populate it with a base HostApplicationServices object:

Imports Autodesk.AutoCAD.Runtime

Imports Autodesk.AutoCAD.DatabaseServices


'<Assembly: SecuredApplication("<Placeholder for the clear text license>", "<Placeholder for the Autodesk encrypted text license>", "<Placeholder for the client encrypted text license>", "<Placeholder for the client public key>")>

<Assembly: SecuredApplication( _

"THIS IS AN OBJECTDBX (TM) VERSION 2007 CLIENT LICENSE FOR THE EXCLUSIVE USE OF Kean Walmsley. YOUR USE OF OBJECTDBX(TM) IS GOVERNED BY THE SOFTWARE LICENSE INCLUDED IN THE PRODUCT. USE OF THIS SOFTWARE IN VIOLATION OF THE SOFTWARE LICENSE IS A VIOLATION OF U.S. AND/OR INTERNATIONAL COPYRIGHT LAWS AND TREATIES AND YOU MAY BE SUBJECT TO CRIMINAL PENALTIES FOR SUCH USE.", _


' Lines deleted - you will need to be a valid RealDWG license holder to get this text


Public Class MyHostApplicationServices

  Inherits HostApplicationServices


  Public Overrides Function FindFile _

    (ByVal fileName As String, _

    ByVal database As Database, _

    ByVal hint As FindFileHint) _

    As String


    FindFile = Nothing


  End Function

End Class

I've edited the above text to delete my personal RealDWG license keys – you will need to license RealDWG to receive valid license keys of your own for this code to work. The FindFile function is needed to help RealDWG find supporting files such as fonts. As we’re only accessing information about 3D solid objects it is not important for us to implement this completely.

Now we go to the code window for MainForm.vb – either by double-clicking the form, the browse button, or by selecting View Code from the Solution Explorer when right-clicking on the file. We replace the default implementation with the following:

Imports Autodesk.AutoCAD.DatabaseServices

Imports Autodesk.AutoCAD.Runtime


Public Class MainForm


  Private Sub MainForm_Load _

    (ByVal sender As System.Object, _

    ByVal e As System.EventArgs) _

    Handles MyBase.Load


    ' Initialize RealDWG host subsystem


    RuntimeSystem.Initialize _

      (New MyHostApplicationServices(), 1033)

  End Sub


  Private Sub BrowseButton_Click _

    (ByVal sender As System.Object, _

    ByVal e As System.EventArgs) _

    Handles BrowseButton.Click


    Dim dlg As New System.Windows.Forms.OpenFileDialog()

    dlg.InitialDirectory = _

      System.Environment.CurrentDirectory

    dlg.Filter = _

      "DWG files (*.dwg)|*.dwg|All files (*.*)|*.*"


    Dim oc As Cursor = Me.Cursor


    If dlg.ShowDialog() = Windows.Forms.DialogResult.OK Then

      Me.Cursor = Cursors.WaitCursor

      DwgFileToAnalyze.Text = dlg.FileName()

      Me.Refresh()

    End If


    If DwgFileToAnalyze.Text <> "" Then


      ' Let's read the selected DWG


      Dim db As Database = _

        New Database(False, True)

      Using db


        db.ReadDwgFile _

          (DwgFileToAnalyze.Text, _

          IO.FileShare.None, _

          False, Nothing)


        HostApplicationServices.WorkingDatabase = db


        ' Analyze it and display the results


        Dim vol As Double = _

          AnalyzeDatabase(db, SolidListBindingSource)

        TotalVolumeText.Text = _

          "Total Volume = " + vol.ToString()


        Me.Cursor = oc


      End Using


    End If

  End Sub

End Class

Our code is now ready to build. There is, however, one additional step needed for the application to run. When you receive a license key for RealDWG, you should also receive a public license file (client.snk) that needs to be bound to your application. There’s a utility that ships in RealDWG’s Utils folder – bindmgd – which does just that:

bindmgd -b <YourApp>.exe client.snk

Now our application should run. All being well, when we select the DWG file we created earlier, we should now see the grid populated with data extracted from the 3D solids contained within it:

3d_solids_realdwg_app

Figure 6 - our RealDWG application accessing the 3D solid data

October 17, 2007 in AutoCAD, AutoCAD .NET, RealDWG, Training, Visual Studio | Permalink | Comments (9) | TrackBack

Updating a specific attribute inside a folder of AutoCAD drawings using RealDWG from .NET

This post finally takes the code last shown in this previous post, migrating it to use RealDWG to update a folder of DWGs without the need for AutoCAD to be installed on the system. A big thanks to Adam Nagy, a member of DevTech working from our Prague office, who turned around my request to convert the code to work with RealDWG in a matter of hours (if not minutes).

Firstly I need to make it clear that this code will not run without both RealDWG installed (I'm using RealDWG 2007, as the file format didn't change between 2007 and 2008) and a "clear text license key" inserted in the code. You'll see some missing lines (lines 9-15), where it needs to be inserted. Once you've licensed RealDWG you can get this key from Autodesk, allowing you to create RealDWG applications using .NET.

Below is the C# code, with the lines that have been added since the previous entry in red. Firstly, a summary of the changes...

There are clearly lines that are no longer needed - these have just been deleted.

In terms of the additional lines: it's a mixture of code that replaces the use of the AutoCAD editor for user-input, with some additional code needed specifically by RealDWG applications.

Lines 22-122 implement the HostApplicationServices class for our application, which RealDWG will call under certain circumstances, such as when it's trying to find particular support files. The FindFile() function has been implemented to search the Windows fonts folder and the RealDWG install folder for any fonts the system needs to adequately load a DWG. You would need to modify the code to point to the folder your application installs fonts into. Additionally I suspect there's work needed to open files that have fonts missing, mapping alternate fonts in their place: this post assumes that the fonts are all available; in a future post we can certainly look at adding support for alternate font mapping.

These fonts are especially important when dealing with alignment of text and attributes. If RealDWG cannot find the fonts on the system, the DWG will be updated with the new text but the attributes will not be positioned correctly (until they are edited in some way inside the AutoCAD editor). This is quite a common issue when developing with RealDWG, but thankfully one that's fairly easy to solve.

Lines 115-145 replace the use of the AutoCAD editor to prompt the user for the important information. In this case we're just using standard console functions for reading/writing data from/to a command window. This is also the reason for lines 174, 197, 208, 223, 233, 243 & 244 changing.

Line 185 sets the working database: this is very important - especially when working with fonts - and without it your attributes will not align properly.

The protocols of the UpdateAttributesInDatabase() and UpdateAttributesInBlock() functions have also been updated to include the static keyword, although I didn't mark those lines in red as they should probably have been static before. :-)

    1 using Autodesk.AutoCAD.DatabaseServices;

    2 using Autodesk.AutoCAD.Runtime;

    3 using System.Reflection;

    4 using System.IO;

    5 using System;

    6

    7 [assembly: SecuredApplication(

    8 @"THIS IS AN OBJECTDBX (TM) VERSION 2007 CLIENT LICENSE FOR THE EXCLUSIVE USE OF Kean Walmsley. YOUR USE OF OBJECTDBX(TM) IS GOVERNED BY THE SOFTWARE LICENSE INCLUDED IN THE PRODUCT. USE OF THIS SOFTWARE IN VIOLATION OF THE SOFTWARE LICENSE IS A VIOLATION OF U.S. AND/OR INTERNATIONAL COPYRIGHT LAWS AND TREATIES AND YOU MAY BE SUBJECT TO CRIMINAL PENALTIES FOR SUCH USE.",

   16

   17 namespace AttributeUpdater

   18 {

   19   class Program

   20   {

   21     #region RealDWG

   22     class MyHost : HostApplicationServices

   23     {

   24       private string SearchPath(string fileName)

   25       {

   26         // check if the file is already with full path

   27         if (System.IO.File.Exists(fileName))

   28           return fileName;

   29

   30         // check application folder

   31         string applicationPath =

   32           Path.GetDirectoryName(

   33             Assembly.GetExecutingAssembly().Location

   34           );

   35         if (File.Exists(applicationPath + "\\" + fileName))

   36           return applicationPath + "\\" + fileName;

   37

   38         // search folders in %PATH%

   39         string []paths =

   40           Environment.GetEnvironmentVariable(

   41             "Path").Split(new char[]{';'}

   42           );

   43         foreach (string path in paths)

   44         {

   45           // some folders end with \\, some don't

   46           string validatedPath

   47             = Path.GetFullPath(path + "\\" + fileName);

   48           if (File.Exists(validatedPath))

   49             return validatedPath;

   50         }

   51

   52         // check the Fonts folders

   53         string systemFonts =

   54           Environment.GetEnvironmentVariable(

   55             "SystemRoot"

   56           ) + "\\Fonts\\";

   57         if (File.Exists(systemFonts + fileName))

   58           return systemFonts + fileName;

   59

   60         string rdwgFonts =

   61           "C:\\Program Files\\Autodesk RealDWG 2007\\Fonts\\";

   62         if (File.Exists(rdwgFonts + fileName))

   63           return rdwgFonts + fileName;

   64

   65         return "";

   66       }

   67

   68       public override string FindFile(

   69         string fileName,

   70         Database database,

   71         FindFileHint hint

   72       )

   73       {

   74         // add extension if needed

   75         if (!fileName.Contains("."))

   76         {

   77           string extension;

   78           switch (hint)

   79           {

   80             case FindFileHint.CompiledShapeFile:

   81               extension = ".shx";

   82               break;

   83             case FindFileHint.TrueTypeFontFile:

   84               extension = ".ttf";

   85               break;

   86             case FindFileHint.PatternFile:

   87               extension = ".pat";

   88               break;

   89             case FindFileHint.ArxApplication:

   90               extension = ".dbx";

   91               break;

   92             case FindFileHint.FontMapFile:

   93               extension = ".fmp";

   94               break;

   95             case FindFileHint.XRefDrawing:

   96               extension = ".dwg";

   97               break;

   98             // Fall through. These could have

   99             // various extensions

  100             case FindFileHint.FontFile:         

  101             case FindFileHint.EmbeddedImageFile:

  102             default:

  103               extension = "";

  104               break;

  105           }

  106

  107           fileName += extension;

  108         }

  109

  110         return SearchPath(fileName);

  111       }

  112     }

  113     #endregion

  114

  115     static void Main(string[] args)

  116     {

  117       // RealDWG specific

  118       RuntimeSystem.Initialize(new MyHost(), 1033);

  119

  120       // Have the user choose the block and attribute

  121       // names, and the new attribute value

  122

  123       System.Console.Write(

  124         "\nEnter folder containing DWGs to process: "

  125       );

  126       string pathName =

  127         System.Console.ReadLine().ToUpper();

  128

  129       System.Console.Write(

  130         "\nEnter name of block to search for: "

  131       );

  132       string blockName =

  133         System.Console.ReadLine().ToUpper();

  134

  135       System.Console.Write(

  136         "\nEnter tag of attribute to update: "

  137       );

  138       string attbName =

  139         System.Console.ReadLine().ToUpper();

  140

  141       System.Console.Write(

  142         "\nEnter new value for attribute: "

  143       );

  144       string attbValue =

  145         System.Console.ReadLine().ToUpper();

  146

  147       string[] fileNames =

  148         Directory.GetFiles(pathName,"*.dwg");

  149

  150       // We'll use some counters to keep track

  151       // of how the processing is going

  152

  153       int processed = 0, saved = 0, problem = 0;

  154

  155       foreach (string fileName in fileNames)

  156       {

  157         if (fileName.EndsWith(

  158               ".dwg",

  159               StringComparison.CurrentCultureIgnoreCase

  160             )

  161         )

  162         {

  163           string outputName =

  164             fileName.Substring(

  165               0,

  166               fileName.Length - 4) +

  167             "_updated.dwg";

  168

  169           Database db = new Database(false, true);

  170           using (db)

  171           {

  172             try

  173             {

  174               System.Console.WriteLine(

  175                 "\n\nProcessing file: " + fileName

  176               );

  177

  178               db.ReadDwgFile(

  179                 fileName,

  180                 FileShare.ReadWrite,

  181                 false,

  182                 ""

  183               );

  184

  185               MyHost.WorkingDatabase = db;

  186

  187               int attributesChanged =

  188                 UpdateAttributesInDatabase(

  189                   db,

  190                   blockName,

  191                   attbName,

  192                   attbValue

  193                 );

  194

  195               // Display the results

  196

  197               System.Console.WriteLine(

  198                 "\nUpdated {0} instance{1} of " +

  199                 "attribute {2}.",

  200                 attributesChanged,

  201                 attributesChanged == 1 ? "" : "s",

  202                 attbName

  203               );

  204

  205               // Only save if we changed something

  206               if (attributesChanged > 0)

  207               {

  208                 System.Console.WriteLine(

  209                   "\nSaving to file: {0}", outputName

  210                 );

  211

  212                 db.SaveAs(

  213                   outputName,

  214                   DwgVersion.Current

  215                 );

  216

  217                 saved++;

  218               }

  219               processed++;

  220             }

  221             catch (System.Exception ex)

  222             {

  223               System.Console.WriteLine(

  224                 "\nProblem processing file: {0} - \"{1}\"",

  225                 fileName,

  226                 ex.Message

  227               );

  228               problem++;

  229             }

  230           }

  231         }

  232       }

  233       System.Console.WriteLine(

  234         "\n\nSuccessfully processed {0} files, of which {1} had " +

  235         "attributes to update and an additional {2} had errors " +

  236         "during reading/processing." +

  237         "\n[Press ENTER to close window]",

  238         processed,

  239         saved,

  240         problem

  241       );

  242

  243       // Delay the closing of the command prompt

  244       System.Console.Read();

  245     }

  246

  247     private static int UpdateAttributesInDatabase(

  248           Database db,

  249           string blockName,

  250           string attbName,

  251           string attbValue

  252         )

  253     {

  254       // Get the IDs of the spaces we want to process

  255       // and simply call a function to process each

  256

  257       ObjectId msId, psId;

  258

  259       Transaction tr =

  260         db.TransactionManager.StartTransaction();

  261       using (tr)

  262       {

  263         BlockTable bt =

  264           (BlockTable)tr.GetObject(

  265             db.BlockTableId,

  266             OpenMode.ForRead

  267           );

  268         msId =

  269           bt[BlockTableRecord.ModelSpace];

  270         psId =

  271           bt[BlockTableRecord.PaperSpace];

  272

  273         // Not needed, but quicker than aborting

  274         tr.Commit();

  275       }

  276       int msCount =

  277         UpdateAttributesInBlock(

  278           db,

  279           msId,

  280           blockName,

  281           attbName,

  282           attbValue

  283         );

  284       int psCount =

  285         UpdateAttributesInBlock(

  286           db,

  287           psId,

  288           blockName,

  289           attbName,

  290           attbValue

  291         );

  292       return msCount + psCount;

  293     }

  294

  295     private static int UpdateAttributesInBlock(

  296       Database db,

  297       ObjectId btrId,

  298       string blockName,

  299       string attbName,

  300       string attbValue

  301     )

  302     {

  303       // Will return the number of attributes modified

  304

  305       int changedCount = 0;

  306

  307       Transaction tr =

  308         db.TransactionManager.StartTransaction();

  309       using (tr)

  310       {

  311         BlockTableRecord btr =

  312           (BlockTableRecord)tr.GetObject(

  313             btrId,

  314             OpenMode.ForRead

  315           );

  316

  317         // Test each entity in the container...

  318

  319         foreach (ObjectId entId in btr)

  320         {

  321           Entity ent =

  322             tr.GetObject(entId, OpenMode.ForRead)

  323             as Entity;

  324

  325           if (ent != null)

  326           {

  327             BlockReference br = ent as BlockReference;

  328             if (br != null)

  329             {

  330               BlockTableRecord bd =

  331                 (BlockTableRecord)tr.GetObject(

  332                   br.BlockTableRecord,

  333                   OpenMode.ForRead

  334               );

  335

  336               // ... to see whether it's a block with

  337               // the name we're after

  338

  339               if (bd.Name.ToUpper() == blockName)

  340               {

  341                 // Check each of the attributes...

  342

  343                 foreach (

  344                   ObjectId arId in br.AttributeCollection

  345                 )

  346                 {

  347                   DBObject obj =

  348                     tr.GetObject(

  349                       arId,

  350                       OpenMode.ForRead

  351                     );

  352

  353                   AttributeReference ar =

  354                     obj as AttributeReference;

  355                   if (ar != null)

  356                   {

  357                     // ... to see whether it has

  358                     // the tag we're after

  359

  360                     if (ar.Tag.ToUpper() == attbName)

  361                     {

  362