TheSwamp
Code Red => .NET => Topic started by: mkweaver 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.
[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]
-
Dim stInsert As New myBlockRef
If you make a new one it won't remember the old stuff.
-
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:
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?
-
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 (http://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.
-
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.
-
I have been looking at the same thing for a while:
Try this
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 (http://www.theswamp.org/index.php?topic=25827.msg310969#msg310969)
Sorry, forgot to convert the GetBlockReferencesByName to VB:
''' <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
-
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.
-
Is there a reason you aren't using the Sheet Set Manager?
-
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)
-
(http://)
-
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.
-
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.