Author Topic: There must be a better way - at least one that won't crash:-/  (Read 4576 times)

0 Members and 1 Guest are viewing this topic.

mkweaver

  • Bull Frog
  • Posts: 352
There must be a better way - at least one that won't crash:-/
« on: November 28, 2008, 03:15:26 PM »
Given an objectid for a blockref with attributes, I am trying to create a list (ArrayList) of block insertions and their attributes using the structures below.

I know that I should be using classes instead of structures, but that will have to be a future task.

Suggestions are appreciated.

Code: [Select]
[color=blue]  Public Structure myBlockRef[/color]
    Dim strName As String
    Dim InsertionPoint As Autodesk.AutoCAD.Geometry.Point3d
    Dim Attributes As System.Collections.Generic.List(Of myAttribute)
    Dim id As ObjectId
[color=blue]  End Structure

  Public Structure myAttribute[/color]
    Dim strSourceBlockName As String
    Dim strTag As String
    Dim strValue As String
    Dim id As ObjectId
[color=blue]  End Structure[/color]

[color=blue]Private Function GetAttributes(ByRef coloIDs As ArrayList) As ArrayList[/color]
    Dim oID As ObjectId
    Dim myDb As Database
    Dim myTransMan As DatabaseServices.TransactionManager
    Dim myTrans As DatabaseServices.Transaction
    Dim oInsert As DatabaseServices.BlockReference
    For Each oID In coloIDs
      myDb = oID.Database
      myTransMan = myDb.TransactionManager
      myTrans = myTransMan.StartTransaction
      oInsert = oID.GetObject(OpenMode.ForRead, False, True)
      Dim stInsert As New myBlockRef
      stInsert.strName = oInsert.BlockName
      stInsert.InsertionPoint = oInsert.Position
      stInsert.id = oID
      Dim colAtts As Object = oInsert.AttributeCollection
      For Each oidAtt As ObjectId In colAtts
        Dim stThisAtt As New myAttribute
        Dim oAtt As New AttributeReference
        oAtt = oidAtt.GetObject(OpenMode.ForRead, False, True)
        stThisAtt.strSourceBlockName = oInsert.Name
        stThisAtt.id = oAtt.ObjectId
        stThisAtt.strTag = oAtt.Tag
        If oAtt.IsMTextAttribute Then
          stThisAtt.strValue = oAtt.MTextAttribute.ToString
        Else
          stThisAtt.strValue = oAtt.TextString
        End If
[color=yellow]        'TODO - the next statement fails, figure out why[/color]
        stInsert.Attributes.Add(stThisAtt)
        oAtt.Dispose()
      Next
      GetAttributes.Add(stInsert)
    Next
    oInsert.Dispose()
    myTrans.Abort()
    myTrans.Dispose()
    myTransMan.Dispose()
    myDb.Dispose()
[color=blue]  End Function
[/color]

Bryco

  • Water Moccasin
  • Posts: 1882
Re: There must be a better way - at least one that won't crash:-/
« Reply #1 on: November 28, 2008, 06:57:10 PM »
Dim stInsert As New myBlockRef
If you make a new one it won't remember the old stuff.

mkweaver

  • Bull Frog
  • Posts: 352
Re: There must be a better way - at least one that won't crash:-/
« Reply #2 on: November 28, 2008, 07:28:22 PM »
Dim stInsert As New myBlockRef
If you make a new one it won't remember the old stuff.
I appreciate the input, but I'm already doing that:
Code: [Select]
    For Each oID In coloIDs
      myDb = oID.Database
      myTransMan = myDb.TransactionManager
      myTrans = myTransMan.StartTransaction
      oInsert = oID.GetObject(OpenMode.ForRead, False, True)
[color=yellow]      Dim stInsert As New myBlockRef[/color]
      stInsert.strName = oInsert.BlockName
      stInsert.InsertionPoint = oInsert.Position
      stInsert.id = oID
      Dim colAtts As Object = oInsert.AttributeCollection
      For Each oidAtt As ObjectId In colAtts
Any other ideas?

Bryco

  • Water Moccasin
  • Posts: 1882
Re: There must be a better way - at least one that won't crash:-/
« Reply #3 on: November 28, 2008, 11:53:36 PM »
I don't have vb to test it, I was thinking the opposite, that you were trying to store everything in the structure, so the new word wouldn't help.
I've found a jagged array like the one usedhttp://www.theswamp.org/index.php?topic=14068.msg310950#msg310950 is almost as easy to use as a collection is in vba. I don't know how inefficient it is, but it works fine and some code is not used enough to mess with the whole class thang.

TonyT

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #4 on: November 29, 2008, 02:59:08 AM »
Given an objectid for a blockref with attributes, I am trying to create a list (ArrayList) of block insertions and their attributes using the structures below.

I know that I should be using classes instead of structures, but that will have to be a future task.

Suggestions are appreciated.


Well there's certainly better ways, but its difficult to suggest
anything without knowing more about the the context, and
what you need to do with the data you want to store in your
structs.

There's lots of ways to store and manipulate data in .NET.
Each can be more or less suitable for a given task. Some of
the parameters that determine that include things like how
frequently you need to access the data, do you need to
quickly locate data given some key, and so forth.

In the mean time, you might want to look into exception
handling and termination handling (try/catch and try/finally)
to ensure your objects are being disposed if an exception
occurs, because the code you show doesn't take any of
those precautions. Most of the AutoCAD .NET code you find
floating around here and elsewhere can serve as examples
that show how to deal with exceptions, and ensure that
disposable objects are disposed.

Exception handling and dealing with disposable objects are
more basic aspects of .NET programming, that one should
become familiar with before progressing to more advanced
things like desiging data structures.

Look into the 'Using' statement for automatically disposing
objects, and look at some of the code posted here to see
how its used.

The other thing you should consider, regardless of whether
you use structs or classes, is to encapsulate assignment of
members in the class/struct itself, rather than doing that
from the outside.

For example, you can define a constructor for a class or
a struct, that takes arguments which receive the values
that are assigned to the fields, and does the assignment
in the constructor. I'm not even sure if legacy VB allows
you to write constructors for structs, and if it doesn't
then it would be a new concept for someone migrating
from legacy VB, that they should become familar with.


mcarson

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #5 on: November 29, 2008, 06:31:30 AM »
I have been looking at the same thing for a while:

Try this
Code: [Select]

Imports AcAp = Autodesk.AutoCAD.ApplicationServices
Imports AcDb = Autodesk.AutoCAD.DatabaseServices
Imports AcGe = Autodesk.AutoCAD.Geometry

Class Export
    ''' <summary>
    ''' List of all block references to export
    ''' </summary>
    ''' <remarks></remarks>
    Private mBlockDetails As New List(Of BlockDetails)

    ''' <summary>
    ''' Attribute Details type
    ''' </summary>
    ''' <remarks>Allows only the tag and value of an attribute reference to be stored</remarks>
    Private Structure AttributeDetails
      Dim AttributeTag As String
      Dim AttributeValue As String
    End Structure

    ''' <summary>
    ''' The Block Details type
    ''' </summary>
    ''' <remarks>Allows the name, coordinates, and attribute values of block references</remarks>
    Private Structure BlockDetails
      Dim Name As String
      Dim x As Double
      Dim y As Double
      Dim z As Double
      Dim Attributes As List(Of AttributeDetails)
    End Structure


    ''' <summary>
    ''' Reads block detail information from block references of a given name
    ''' </summary>
    ''' <param name="blockname">Input for the block name</param>
    ''' <remarks></remarks>
    Private Sub GetDetails(ByVal blockname As String)
      'Get the usual stuff
      Dim db As AcDb.Database = AcAp.Application.DocumentManager.MdiActiveDocument.Database
      Dim tm As AcDb.TransactionManager = db.TransactionManager

      'Create a new list to store the block references found
      Dim brlist As New List(Of AcDb.BlockReference)

      'Clear the class level block details variable
      mBlockDetails.Clear()

      'Start a new transaction (read-only)
      Using tr As AcDb.Transaction = tm.StartTransaction
        'Get all the block references
        brlist = GetBlockReferencesByName(db, tr, Autodesk.AutoCAD.DatabaseServices.OpenMode.ForRead, blockname)
        'Loop through the returned list
        For Each br As AcDb.BlockReference In brlist
          'Get the insertion point of the block
          Dim insertp As AcGe.Point3d = br.Position
          'Get the x ordinate
          Dim x As Double = insertp.X
          'Get the y ordinate
          Dim y As Double = insertp.Y
          'Get the z ordinate
          Dim z As Double = insertp.Z

          'Create a new block details type and add values
          Dim blkdet As New BlockDetails
          blkdet.Name = blockname
          blkdet.x = x
          blkdet.y = y
          blkdet.z = z
          'Get all attributes from the block reference
          blkdet.Attributes = GetAttributes(br.ObjectId)
          'Add to class level block details
          mBlockDetails.Add(blkdet)
        Next
        tr.Commit()
      End Using
    End Sub

    Private Function GetAttributes(ByVal BlockRefId As AcDb.ObjectId) As List(Of AttributeDetails)
      Dim attdlist As New List(Of AttributeDetails)
      Dim db As AcDb.Database = AcAp.Application.DocumentManager.MdiActiveDocument.Database
      Dim tm As AcDb.TransactionManager = db.TransactionManager
      Using tr As AcDb.Transaction = tm.StartTransaction
        Dim br As AcDb.BlockReference = DirectCast(tr.GetObject(BlockRefId, AcDb.OpenMode.ForRead), AcDb.BlockReference)
        For Each attroid As AcDb.ObjectId In br.AttributeCollection
  Dim attr As AcDb.AttributeReference = DirectCast(tr.GetObject(attroid, AcDb.OpenMode.ForRead), AcDb.AttributeReference)
  Dim atttag As String = attr.Tag
  Dim attvalue As String = attr.TextString
    Dim attd As New AttributeDetails
  attd.AttributeTag = atttag
  attd.AttributeValue = attvalue
  attdlist.Add(attd)
Next
Return attdlist
      End Using
    End Function
End Class
However I keep running into the eNotErased bug and get unexpected results.
See http://www.theswamp.org/index.php?topic=25827.msg310969#msg310969

Sorry, forgot to convert the GetBlockReferencesByName to VB:

Code: [Select]
''' <summary>
''' Get all block references of a certain name
''' </summary>
''' <param name="db">Input for the database object</param>
''' <param name="tr">Input for the transaction object</param>
''' <param name="openMode">Return for read or write?</param>
''' <param name="blockName">Input for the block name</param>
''' <returns>The collection of block references</returns>
''' <remarks></remarks>
Friend Shared Function GetBlockReferencesByName(ByVal db As AcDb.Database, ByVal tr As AcDb.Transaction, ByVal openMode As AcDb.OpenMode, ByVal blockName As String) As IEnumerable(Of AcDb.BlockReference)

Dim brlist As New List(Of AcDb.BlockReference)
Dim bt As AcDb.BlockTable = DirectCast(tr.GetObject(db.BlockTableId, AcDb.OpenMode.ForRead), AcDb.BlockTable)
Try
If Not bt.Has(blockName) Then
Throw New AcRx.Exception(AcRx.ErrorStatus.InvalidBlockName)
End If
Dim btr As AcDb.BlockTableRecord = DirectCast(tr.GetObject(bt.Item(blockName), AcDb.OpenMode.ForRead), AcDb.BlockTableRecord)
For Each oid As AcDb.ObjectId In btr.GetBlockReferenceIds(True, False)
Dim br As AcDb.BlockReference = DirectCast(tr.GetObject(oid, openMode), AcDb.BlockReference)
brlist.Add(br)
Next
Return brlist
Catch ex As AcRx.Exception
AcAp.Application.ShowAlertDialog(ex.Message)
End Try
Return brlist
End Function
« Last Edit: November 29, 2008, 06:43:02 AM by mcarson »

mkweaver

  • Bull Frog
  • Posts: 352
Re: There must be a better way - at least one that won't crash:-/
« Reply #6 on: November 29, 2008, 01:09:40 PM »
I appreciate the pointers I'm receiving here.

A bit of background is in order.

This procedure is part of a larger procedure (duh) I am attempting to convert from lisp to vb.net, that does the following:

  • Reads a drawing index from the cover sheet of a design package.  This drawing index is an autoCAD table in a specific format.  The drawing filenames are derived from the sheet numbers on this table.
  • The routine then scans each drawing in turn and gathers specific information from the title block (an attributed block insertion).
  • This information is then updated on the drawing index on the cover sheet.

To speed this operation up, the information read from the drawings is written to disk, including the drawing file's time and date.  On subsequent runs, the data is read back off the disk and the individual dwgs are only re-scanned if the time and date has changed since the last scan.

The application, as written in lisp, has proven to be a great time saver, coordinating the information on the individual drawings and the cover sheet.  Speed has been reasonable, scanning drawings at a rate of approximately three drawings per second.

There are a couple of reasons for the dot net rewrite.  First, this is a learning exercise, second, I'll take any speed improvement I can get, and third, I would like to add the ability to push revised attribute values back to the individual drawing files (such as issue date).

The subject of this thread is intended to gather the attribute values from all attributed blocks in a given drawing (as opposed to just the title block) to give it greater utility for other applications.
Again, I appreciate the suggestions given.

sinc

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #7 on: November 29, 2008, 02:06:22 PM »
Is there a reason you aren't using the Sheet Set Manager?

mcarson

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #8 on: November 29, 2008, 03:14:55 PM »
The Sheet Set Manager would be a good direction to take. Why reinvent the wheel - The SSM does most, if not all, of your requirements.

On your idea for a utility to export all blocks and attributes, download the attached DLL and netload it into AutoCAD.
Use 'coordinateexport' (without the quotes) to execute the program

Bear in mind that this is for testing only; you asked your question half-way-through my ideas of a similar utility.

I will be happy to post the source, if required (you could also use reflector)

(Another point - C# is the way to go) I code in both (VB for easier upgrading from VBA, C# for new projects)
« Last Edit: November 29, 2008, 03:27:01 PM by mcarson »

mcarson

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #9 on: November 29, 2008, 03:19:25 PM »

TonyT

  • Guest
Re: There must be a better way - at least one that won't crash:-/
« Reply #10 on: November 29, 2008, 04:40:08 PM »
There's nothing wrong with approaching this as a
learning excercise, but also keep in mind that what
you propose comes very close to reinventing AutoCAD's
Data Extraction tool, which has become much more
powerful since gaining the ability to extract much more
than just block attributes (it can extract almost any
property of a managed wrapper), and the example that
mcarson posted comes even closer to it.

Ultimately, how to approach the problem depends on how
you're persisting the (e.g., serialization of class/structs,
or database tables).  For example, if you are storing the
data in database tables, then a DataSet with two tables
(one for blocks and one for attributes) would probably be
the best approach.

The other question is if you are writing the data to disk,
then ObjectIds would not be valid in a subsequent editing
session (but of course, handles would, assuming there's
also a reference to the drawing containing the objects).



I appreciate the pointers I'm receiving here.

A bit of background is in order.

This procedure is part of a larger procedure (duh) I am attempting to convert from lisp to vb.net, that does the following:

  • Reads a drawing index from the cover sheet of a design package.  This drawing index is an autoCAD table in a specific format.  The drawing filenames are derived from the sheet numbers on this table.
  • The routine then scans each drawing in turn and gathers specific information from the title block (an attributed block insertion).
  • This information is then updated on the drawing index on the cover sheet.

To speed this operation up, the information read from the drawings is written to disk, including the drawing file's time and date.  On subsequent runs, the data is read back off the disk and the individual dwgs are only re-scanned if the time and date has changed since the last scan.

The application, as written in lisp, has proven to be a great time saver, coordinating the information on the individual drawings and the cover sheet.  Speed has been reasonable, scanning drawings at a rate of approximately three drawings per second.

There are a couple of reasons for the dot net rewrite.  First, this is a learning exercise, second, I'll take any speed improvement I can get, and third, I would like to add the ability to push revised attribute values back to the individual drawing files (such as issue date).

The subject of this thread is intended to gather the attribute values from all attributed blocks in a given drawing (as opposed to just the title block) to give it greater utility for other applications.
Again, I appreciate the suggestions given.

mkweaver

  • Bull Frog
  • Posts: 352
Re: There must be a better way - at least one that won't crash:-/
« Reply #11 on: November 29, 2008, 06:49:08 PM »
I can't extract directly to my drawing index because of the way some of the attributes are done in the title blocks.  For example, the drawing description is a concatenation of four attributes that may or may not have values.  Another example is the fact that there are attributes for up to 21 revisions and I only need the last one to go into the drawing index.

Now, I wonder if I can drive the data extraction tool programmatically, and send the data to a file (or database) then I can read that instead of the drawings themselves.

Aside from the above comments, I have multiple uses for a way to extract attribute values from my drawings and have used the lisp version of this routine multiple times.

I agree that storing the data in a multi-table database is probably the best long term approach, though I'm still left with the need to scan the drawings for the attribute values so I can put them in the database.

Thanks for the heads up about the ObjectIds not being valid in subsequent editing sessions:-(  In my case I can't use handles either, since we audit often (are encouraged to audit each time we save) and audit rearranges the handles.