Author Topic: How do I set attribute values after inserting an external block  (Read 6804 times)

0 Members and 1 Guest are viewing this topic.

BrianM

  • Guest
I' m struggling with setting attribute values on an external block. I've posted some code below that I've been experimenting with.
Most of this code came from either this site of the Autodesk site.
The block is an external drawing with an attribute. The code below inserts the drawing, but doesn't set the attribute. If I explode the block, the attribute tag is there. Any help would be greatly appreciated.


Code: [Select]
Dim hingeBlockFilePath As String = "C:\cad\support\ads\ss edge\DOORNUM.dwg"


            Dim inspt As New Point3d(7.53, 10.35, 0)

            Dim newBlockName As String = "DOORNUMBER"

            Dim doc As Document = Application.DocumentManager.MdiActiveDocument
            Using docloc As DocumentLock = doc.LockDocument


                Using trans As Transaction = doc.TransactionManager.StartTransaction


                    'Read Block Drawing's Database
                    Dim db As New Database(False, False)
                    db.ReadDwgFile(hingeBlockFilePath, FileOpenMode.OpenForReadAndAllShare, True, vbNullString)

                    'insert it as a new block
                    Dim idBTR As ObjectId = doc.Database.Insert(newBlockName, db, True)


                    Using bt As BlockTable = trans.GetObject(doc.Database.BlockTableId, OpenMode.ForRead), _
                    btr As BlockTableRecord = trans.GetObject(bt(BlockTableRecord.PaperSpace), OpenMode.ForWrite), _
                   bref As BlockReference = New BlockReference(inspt, idBTR)


                        btr.AppendEntity(bref)
                        trans.TransactionManager.AddNewlyCreatedDBObject(bref, True)


                        Dim blkTblR As BlockTableRecord = btr.Id.GetObject(OpenMode.ForRead)
                        For Each objId As ObjectId In blkTblR
                            Dim obj As DBObject = objId.GetObject(OpenMode.ForRead)
                            If TypeOf obj Is AttributeDefinition Then

                                Dim ad As AttributeDefinition = objId.GetObject(OpenMode.ForRead)
                                Dim ar As AttributeReference = New AttributeReference()
                                ar.SetAttributeFromBlock(ad, bref.BlockTransform)
                                ar.Position = ad.Position.TransformBy(bref.BlockTransform)
                                bref.AttributeCollection.AppendAttribute(ar)
                                trans.AddNewlyCreatedDBObject(ar, True)

                            End If

                        Next
                        Dim myAttColl As AttributeCollection = bref.AttributeCollection
                        For Each myAttRefId As ObjectId In myAttColl
                            Dim myAttRef As AttributeReference = myAttRefId.GetObject(OpenMode.ForWrite)


                            If myAttRef.Tag = "DRNUM" Then myAttRef.TextString = "TEST"

                        Next

                        trans.Commit()
                    End Using
                End Using


            End Using

huiz

  • Swamp Rat
  • Posts: 919
  • Certified Prof C3D
Re: How do I set attribute values after inserting an external block
« Reply #1 on: May 23, 2013, 11:11:27 AM »
You can find an example here of a complete function with attributes:

http://www.theswamp.org/index.php?topic=31859.msg397810#msg397810


The conclusion is justified that the initialization of the development of critical subsystem optimizes the probability of success to the development of the technical behavior over a given period.

BrianM

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #2 on: May 23, 2013, 02:59:50 PM »
Thanks for the link. After studying my code a bit more, I realized I had a couple obvious mistakes. The new code is below. I would greatly appreciate any recommendations on changes that could be made to make it more efficient or better organized.

Code: [Select]
Public Sub Main()

            Dim hingeBlockFilePath As String = "C:\cad\support\ads\ss edge\DOORNUM.dwg"


            Dim inspt As New Point3d(7.53, 10.35, 0)

            Dim newBlockName As String = "DOORNUMBER"

            Dim doc As Document = Application.DocumentManager.MdiActiveDocument
            Using docloc As DocumentLock = doc.LockDocument


                Dim curdb As Database = HostApplicationServices.WorkingDatabase

                Using trans As Transaction = doc.TransactionManager.StartTransaction
                    'Read Block Drawing's Database
                    Dim bt As BlockTable = curdb.BlockTableId.GetObject(OpenMode.ForRead)

                    If bt.Has(newBlockName) = False Then
                        Dim db As New Database(False, False)
                        db.ReadDwgFile(hingeBlockFilePath, FileOpenMode.OpenForReadAndAllShare, True, vbNullString)
                        Dim idBTR As ObjectId = curdb.Insert(newBlockName, db, True)
                        db.Dispose()
                    End If


                    Dim btr As BlockTableRecord = bt(newBlockName).GetObject(OpenMode.ForRead)
                    Dim btrPS As BlockTableRecord = bt(BlockTableRecord.PaperSpace).GetObject(OpenMode.ForWrite)

                    Dim bref As New BlockReference(inspt, btr.ObjectId)
                    btrPS.AppendEntity(bref)
                    trans.AddNewlyCreatedDBObject(bref, True)


                    Dim blkTblR As BlockTableRecord = btr.Id.GetObject(OpenMode.ForRead)
                    For Each objId As ObjectId In blkTblR
                        Dim obj As DBObject = objId.GetObject(OpenMode.ForRead)
                        If TypeOf obj Is AttributeDefinition Then
                            Dim ad As AttributeDefinition = objId.GetObject(OpenMode.ForRead)
                            Dim ar As AttributeReference = New AttributeReference()
                            ar.SetAttributeFromBlock(ad, bref.BlockTransform)
                            ar.Position = ad.Position.TransformBy(bref.BlockTransform)
                            bref.AttributeCollection.AppendAttribute(ar)
                            trans.AddNewlyCreatedDBObject(ar, True)

                        End If

                    Next
                    Dim myAttColl As AttributeCollection = bref.AttributeCollection
                    For Each myAttRefId As ObjectId In myAttColl
                        Dim myAttRef As AttributeReference = myAttRefId.GetObject(OpenMode.ForWrite)


                        If myAttRef.Tag = "DRNUM" Then myAttRef.TextString = "test"

                    Next




                    trans.Commit()
                End Using
            End Using




        End Sub

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: How do I set attribute values after inserting an external block
« Reply #3 on: May 23, 2013, 04:29:52 PM »
blkTblR is the same as btr so redundant to reopen the object to iterate over the attribute definitions. 

If you are setting attributes in the same command as inserting the block it is handy to make a dictionary of attribute tag and object id so you don't need to iterate through the attribute collection on the block to set the values.  If you do however need to iterate over the entire attribute collection it is best to first open the attribute for read and if it is one of the ones you wish to set use UpgradeOpen() then set the value

BrianM

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #4 on: May 28, 2013, 04:41:33 PM »
Will,
Thanks for the suggestions.
Do you have any code examples for creating a dictionary of attribute tags and object id?


Thanks
Brian

TheMaster

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #5 on: May 29, 2013, 09:22:32 AM »
Will,
Thanks for the suggestions.
Do you have any code examples for creating a dictionary of attribute tags and object id?


Thanks
Brian

No VB code I'm afraid, but if you search this board for "GetObjects&lt;T&gt" (yes, exactly that I'm afraid, since the ability to search through colorized code blocks is damn-near impossible), you will find the GetObjects<T> method (in C#, but which you can translate to VB if someone else hasn't already done it).

With that method, getting the attribute definition tags and objectIds into a Dictionary<string,ObjectId> requires little more than this (with an active transaction):

Code: [Select]

     BlockTableRecord btr = // assign to a BlockTableRecord

     Dictionary<string, ObjectId> attdefs =
        btr.GetObjects<AttributeDefinition>()
           .ToDictionary(
               a => a.Tag,
               a => a.ObjectId,
               StringComparer.OrdinalIgnoreCase );

« Last Edit: May 29, 2013, 09:35:25 AM by TT »

BrianM

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #6 on: May 29, 2013, 10:12:21 PM »
TT,

Thank you for the suggestion. I'll do some experimenting with that code.

csharpbird

  • Newt
  • Posts: 64
Re: How do I set attribute values after inserting an external block
« Reply #7 on: May 30, 2013, 05:48:27 AM »
Hi,Tony
It seems that BlockTableRecord doesnot support your GetObjects method.

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: How do I set attribute values after inserting an external block
« Reply #8 on: May 30, 2013, 07:43:34 AM »
Hi,Tony
It seems that BlockTableRecord doesnot support your GetObjects method.

Are you using Tony's Extension Methods?
Revit 2019, AMEP 2019 64bit Win 10

fixo

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #9 on: May 31, 2013, 08:43:24 AM »
Quote
It seems that BlockTableRecord doesnot support your GetObjects method.
Just a quick test, seems to me it works on my A2010
Code: [Select]
  public static class BlockExtensions
    {
        public static T GetObject<T>(this ObjectId id) where T : DBObject
        {
            return id.GetObject(OpenMode.ForRead, false, false) as T;
        }

        public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
        {
            return ids
                .Cast<ObjectId>()
                .Select(id => id.GetObject(OpenMode.ForRead, false, false))
                .OfType<T>();
        }
        public static Dictionary<string, ObjectId> GetAttributeDefinitions(this BlockReference br)
        {
            BlockTableRecord btr = (BlockTableRecord)(br.IsDynamicBlock ? br.BlockTableRecord.GetObject(OpenMode.ForRead,false) : br.DynamicBlockTableRecord.GetObject(OpenMode.ForRead,false));

            Dictionary<string, ObjectId> attdefs =
               btr.GetObjects<AttributeDefinition>()
                  .ToDictionary(
                      a => a.Tag,
                      a => a.ObjectId,
                      StringComparer.OrdinalIgnoreCase);
            return attdefs;
        }
        [CommandMethod("TTD")]
        public static void testAttDefs()
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            try
            {
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    doc.TransactionManager.EnableGraphicsFlush(true);
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead, false, true);


                    PromptEntityOptions peo = new PromptEntityOptions("\nPlease, select block: ");
                    peo.SetRejectMessage("\nYou have to select BlockReference only!");
                    peo.AllowNone = false;
                    peo.AllowObjectOnLockedLayer = true;
                    peo.AddAllowedClass(typeof(BlockReference), false);
                    PromptEntityResult per = ed.GetEntity(peo);
                    if (per.Status != PromptStatus.OK) return;
                    ObjectId objId = per.ObjectId;
                    if (!objId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(BlockReference))))
                    {
                        ed.WriteMessage("\nYou didn't select a BlockReference, please try again...\n");
                        return;
                    }
                    DBObject blkobj = (DBObject)tr.GetObject(objId, OpenMode.ForRead, false);
                    BlockReference bref = blkobj as BlockReference;
                    if (bref == null) return;
                    Dictionary<string, ObjectId> attefs = GetAttributeDefinitions(bref);

                    foreach (KeyValuePair<string, ObjectId> kvp in attefs)
                    {
                        ed.WriteMessage("\n{0}\t{1}", kvp.Key, kvp.Value);
                    }
                    tr.Commit();
                }
            }
            catch (Autodesk.AutoCAD.Runtime.Exception ex)
            {
                ed.WriteMessage("\n" + ex.Message);
            }
        }
}
« Last Edit: May 31, 2013, 09:24:53 AM by fixo »

TheMaster

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #10 on: May 31, 2013, 08:46:42 AM »
Hi,Tony
It seems that BlockTableRecord doesnot support your GetObjects method.

Instead of just grabbing the code, you might want to also try reading my post.

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: How do I set attribute values after inserting an external block
« Reply #11 on: May 31, 2013, 04:37:49 PM »
What I actually had in mind when I made that comment is making a dictionary of attribute references.  When I'm inserting a block and immediately manipulating it I find this to be useful.

Code: [Select]
      Dim attributes as Dictionary<string, ObjectId> = new Dictionary<string, ObjectId>()
                    For Each objId As ObjectId In blkTblR
                        Dim obj As DBObject = objId.GetObject(OpenMode.ForRead)
                        If TypeOf obj Is AttributeDefinition Then
                            Dim ad As AttributeDefinition = objId.GetObject(OpenMode.ForRead)
                            Dim ar As AttributeReference = New AttributeReference()
                            ar.SetAttributeFromBlock(ad, bref.BlockTransform)
                            ar.Position = ad.Position.TransformBy(bref.BlockTransform)
                            attributes.Add(ar.Tag, bref.AttributeCollection.AppendAttribute(ar))
                            trans.AddNewlyCreatedDBObject(ar, True)

                        End If

                    Next

which can later be used similar to

Code - C#: [Select]
  1.      ((AttributeReference)tr.GetObject(attributes["Tag"],OpenMode.ForWrite)).TextString = "Value";

Sorry for the mash of code as I'm away from my library today.

Before I make the following comments I must trash the code by the OP because it opens each object in the block table record and if it's an attribute definition it opens the object again (very poor understanding of what's going on here... I strongly suggest reading the ARX documentation!) when it could test the RXClass on the ObjectId for type and only open attribute definitions (which is how GetObjects works).  Also the code does not check for constant attribute definitions which do not get a corresponding attribute reference (they make no sense to me as they are just like text.... does anybody know why they exist?)

That said the way I do things may be a few clock cycles faster but GetObjects is much easier to use in application code.  If it had been released under a specific license and not just under general copyright I would use it in my production code.

kaefer

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #12 on: May 31, 2013, 06:56:21 PM »
Code - C#: [Select]
  1.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  2.         {
  3.             return id.GetObject(OpenMode.ForRead, false, false) as T;
  4.         }

Call that, say, TryGetObject, alerting users to its potential to fail silently at the point of invocation, only to throw a null reference later.

Code - vb.net: [Select]
  1.         <ExtensionAttribute()>
  2.         Public Function GetObject(Of T As DBObject)(oId As ObjectId) As T
  3.             Return CType(oId.GetObject(OpenMode.ForRead, False, False), T)
  4.         End Function

Quote
Code - C#: [Select]
  1.         public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
  2.         {
  3.             return ids
  4.                 .Cast<ObjectId>()
  5.                 .Select(id => id.GetObject(OpenMode.ForRead, false, false))
  6.                 .OfType<T>();
  7.         }
  8.  

Certain people abhor passing a non-generic sequence to a functon which will produce run-time errors when fed an argument of the wrong type, like string (IEnumerable of char), when a sequence of ObjectId  is expected. Make it an overload of the specific class.

Code - vb.net: [Select]
  1.         <ExtensionAttribute()>
  2.         Public Iterator Function GetObjects(Of T As DBObject)(btr As BlockTableRecord) As IEnumerable(Of T)
  3.             For Each objectId As ObjectId In btr
  4.                 Dim target As T = TryCast(objectId.GetObject(OpenMode.ForRead, False, False), T)
  5.                 If target IsNot Nothing Then
  6.                     Yield target
  7.                 End If
  8.             Next
  9.         End Function

Many thanks to ILSpy, which decompiles to apparently reasonable VB code. Sorry, no round-trip; the code produced by Microsoft's VB compiler isn't suitable.

Code - vb.net: [Select]
  1.                 Dim attdefs As Dictionary(Of String, ObjectId) =
  2.                     oId.GetObject(Of BlockReference)().
  3.                         BlockTableRecord.
  4.                         GetObject(Of BlockTableRecord)().
  5.                         GetObjects(Of AttributeDefinition)().
  6.                         ToDictionary(
  7.                             Function(a As AttributeDefinition) a.Tag,
  8.                             Function(a As AttributeDefinition) a.ObjectId,
  9.                             StringComparer.OrdinalIgnoreCase)

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How do I set attribute values after inserting an external block
« Reply #13 on: May 31, 2013, 11:26:54 PM »
Also the code does not check for constant attribute definitions which do not get a corresponding attribute reference (they make no sense to me as they are just like text.... does anybody know why they exist?)
I have wondered the same thing.
I thought one time I had a "Ahhh" moment when I saw a reason for a constant attribute, but I can't remember what it was, or if I realized it really wasn't a "Ahhhh" moment and erased the reason from memory, but forgot to erase the "Ahhh" moment from memory.
 
Certain people abhor passing a non-generic sequence to a functon which will produce run-time errors when fed an argument of the wrong type, like string (IEnumerable of char), when a sequence of ObjectId  is expected. Make it an overload of the specific class.
That is the way it always seemed easier for me was creating some generic methods and then more specific for classes up the hierarchy.
 
For example creating generic functions for SymbolTables then more specific for each of the Tables derived from SymbolTable, Most of the time I do not want Dependent(From Xref's) SymbolTableRecords  which can handled differently for the different tables. Can compare the current database to the ObjectIds OriginalDatabase to filter out BlockTableRecords, TextStyles and LineStyles brought in from xrefs. Once a BlockTableRecord was opened still needed to check if it was the block for xref. Had to open LayerTableRecords to check if IsDependent.
 
 
I never really had to deal with situations where I did not know if I was dealing with an object that derived from Entity or not so
 
I like using  & GetEntities<> extension method for BlockTableRecords since they are the only objects that will contain them, and using Entity as a constraint for the generic parameter. Keep code for dealing with locked layers, etc... out of accessing other containers.
 

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How do I set attribute values after inserting an external block
« Reply #14 on: May 31, 2013, 11:37:00 PM »
Getting From BlockReference but should help
 
http://www.theswamp.org/index.php?topic=42591.msg477887#msg477887

kaefer

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #15 on: June 02, 2013, 02:40:18 PM »
For example creating generic functions for SymbolTables then more specific for each of the Tables derived from SymbolTable,

Funny you should mention that. Since SymbolTableRecord doesn't implement IEnumerable, but its dependents are expected to, we could use this loophole.

Code - vb.net: [Select]
  1.         <ExtensionAttribute()>
  2.         Public Iterator Function GetObjects(Of T As DBObject)(str As SymbolTableRecord) As IEnumerable(Of T)
  3.             For Each objectId As ObjectId In CType(str, IEnumerable)
  4.                 Dim target As T = TryCast(objectId.GetObject(OpenMode.ForRead, False, False), T)
  5.                 If target IsNot Nothing Then
  6.                     Yield target
  7.                 End If
  8.             Next
  9.         End Function

Gasty

  • Newt
  • Posts: 90
Re: How do I set attribute values after inserting an external block
« Reply #16 on: June 02, 2013, 06:39:27 PM »
Hi,

The Yield statement  only exists in VB 11 .NET 4.5.

Gaston Nunez

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How do I set attribute values after inserting an external block
« Reply #17 on: June 04, 2013, 01:29:35 AM »
Hi,

The Yield statement  only exists in VB 11 .NET 4.5.

Gaston Nunez

Also 4.0, 3.5, 3.0 &2.0

For example creating generic functions for SymbolTables then more specific for each of the Tables derived from SymbolTable,

Funny you should mention that. Since SymbolTableRecord doesn't implement IEnumerable, but its dependents are expected to, we could use this loophole.

Code - vb.net: [Select]
  1.         <ExtensionAttribute()>
  2.         Public Iterator Function GetObjects(Of T As DBObject)(str As SymbolTableRecord) As IEnumerable(Of T)
  3.             For Each objectId As ObjectId In CType(str, IEnumerable)
  4.                 Dim target As T = TryCast(objectId.GetObject(OpenMode.ForRead, False, False), T)
  5.                 If target IsNot Nothing Then
  6.                     Yield target
  7.                 End If
  8.             Next
  9.         End Function
BlockTableRecord implements IEnumarable, and that's the only SymbolTableRecord off the top of my head I can think of that is a "container"
 

TheMaster

  • Guest
Re: How do I set attribute values after inserting an external block
« Reply #18 on: June 04, 2013, 02:50:12 PM »

Certain people abhor passing a non-generic sequence to a functon which will produce run-time
errors when fed an argument of the wrong type, like string (IEnumerable of char), when a sequence of ObjectId  is expected. Make it an overload of the specific class.


Yes, I suppose that would be me.  I don't see it as being any different than typing an argument as System.Object, when only certain specific types are applicable. 
« Last Edit: June 05, 2013, 07:05:46 AM by TT »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How do I set attribute values after inserting an external block
« Reply #19 on: June 04, 2013, 07:12:44 PM »
To be fair and should of mentioned from early post I made
http://www.theswamp.org/index.php?topic=44641.msg499207#msg499207
 
To thank Tony as it was from comments in a example posted by Tony or how I understood what he was saying. Might have misunderstood his intent, but all I remember it threw an error if it was not derived from SymbolTableRecord and advised to create others for each of the different tables.