Author Topic: Counting nested blocks + regular blocks  (Read 1629 times)

0 Members and 1 Guest are viewing this topic.

Eycki

  • Newt
  • Posts: 41
Counting nested blocks + regular blocks
« on: December 19, 2011, 05:03:08 am »
This is my problem:  I got plenty of associative arrays with dynamic blocks inside the arrays.  And also some other arrays which are exploded.  I've been told that the 2012 associative array from AutoCAD is like an anonymous block.  So is there a possibility to count the nested items from each AssocArray in the drawing?  Additional I want to sum the other similar blocks which aren't in the array.

kaefer

  • Swamp Rat
  • Posts: 562
Re: Counting nested blocks + regular blocks
« Reply #1 on: December 19, 2011, 06:25:00 pm »
I've been told that the 2012 associative array from AutoCAD is like an anonymous block.

Not only that, the items of the array are anonymous blocks too; such that there are two levels of block nesting.

Quote
So is there a possibility to count the nested items from each AssocArray in the drawing?  Additional I want to sum the other similar blocks which aren't in the array.

I can't find any user-servicable parts under the covers of the AA APi, allowing access to the anonymous blocks. Barring this, another possibilöity springs to mind: Inspect all of them and assume any nesting two levels deep is pertinent.

The result may not be pretty.

Code: [Select]
    public class CountAssocBlocksCommands
    {
        // Count Blocks in Modelspace and in Associative Arrays;
        // 0. Modelspace -> 1. Block
        // 0. Modelspace -> 1. AssocArray -> 2. ArrayItem -> 3. Block

        [CommandMethod("CountAssocBlocks")]
        public void CountAssocBlocksCommand()
        {
            Database db = acadApp.DocumentManager.MdiActiveDocument.Database;
            Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                foreach (string msg in stringResults(db))
                {
                    ed.WriteMessage(msg);
                }
                tr.Commit();
            }
        }
        IEnumerable<string> stringResults(Database db)
        {
            return
                getNestedBTRs(SymbolUtilityServices.GetBlockModelSpaceId(db))
                .SelectMany(btr1 =>
                    btr1.IsAnonymous ?
                        btr1.GetObjects<BlockReference>()
                        .Where(bref2 =>
                            bref2.BlockTableRecord
                            .GetObject<BlockTableRecord>().IsAnonymous)
                        .SelectMany(bref2 =>
                            getNestedBTRs(bref2.BlockTableRecord)
                            .Select(btr3 =>
                                new
                                {
                                    OwnerName = btr1.Name,
                                    Name = btr3.Name
                                })) :
                        new[] { new
                        {
                            OwnerName = SymbolUtilityServices.BlockModelSpaceName,
                            Name = btr1.Name
                        }})
                .GroupBy(id => id)
                .Select((k, n) =>
                    String.Format(
                        "\n{0} {1} : {2} ",
                        k.Key.OwnerName,
                        k.Key.Name,
                        k.Count()));
        }
        IEnumerable<BlockTableRecord> getNestedBTRs(ObjectId oid)
        {
            return
                oid
                .GetObject<BlockTableRecord>()
                .GetObjects<BlockReference>()
                .Select(br =>
                    (br.IsDynamicBlock ? br.DynamicBlockTableRecord : br.BlockTableRecord)
                    .GetObject<BlockTableRecord>());
        }
    }
    // Extension methods for generic opening of ObjectIds
    public static class Extensions
    {
        // Opens a DBObject in ForRead mode
        public static T GetObject<T>(this ObjectId id)
            where T : DBObject
        {
            return id.GetObject(OpenMode.ForRead) as T;
        }
        // Opens an untyped collection of ObjectIds as DBObjects in ForRead mode
        public static IEnumerable<T> GetObjects<T>(this System.Collections.IEnumerable ids)
            where T : DBObject
        {
            return
                from ObjectId id in ids
                select id.GetObject<T>() into obj
                where obj != null
                select obj;
        }
    }

Eycki

  • Newt
  • Posts: 41
Re: Counting nested blocks + regular blocks
« Reply #2 on: December 20, 2011, 08:06:29 am »
I've been told that the 2012 associative array from AutoCAD is like an anonymous block.

Not only that, the items of the array are anonymous blocks too; such that there are two levels of block nesting.

Quote
So is there a possibility to count the nested items from each AssocArray in the drawing?  Additional I want to sum the other similar blocks which aren't in the array.

I can't find any user-servicable parts under the covers of the AA APi, allowing access to the anonymous blocks. Barring this, another possibilöity springs to mind: Inspect all of them and assume any nesting two levels deep is pertinent.

The result may not be pretty.


Thx for the help!  I'll do my best to implement this.

kaefer

  • Swamp Rat
  • Posts: 562
Re: Counting nested blocks + regular blocks
« Reply #3 on: December 20, 2011, 09:48:30 am »
I'll do my best to implement this.

You mean re-implement (in a different language)? Just in case, here's the F# version. Which, incidentally, doesn't look too bad.

Code: [Select]
// type test returning 'T option
let ofType<'T> : obj -> _ =
    box
    >> function :? 'T as t -> Some t | _ -> None

// get DBObject for read and cast to 'T, if type parameter present
let getObject<'T when 'T :> DBObject> (oid: ObjectId) =
    oid.GetObject OpenMode.ForRead
    |> unbox<'T>

// get all objects from an untyped IEnumerable, and try cast to 'T
let getObjects<'T when 'T :> DBObject> : System.Collections.IEnumerable -> _ =
    Seq.cast<ObjectId>
    >> Seq.choose (getObject >> ofType<'T>)

let getNestedBTRs =
    getObject<BlockTableRecord>
    >> getObjects<BlockReference>
    >> Seq.map (fun br ->
        if br.IsDynamicBlock then br.DynamicBlockTableRecord
        else br.BlockTableRecord
        |> getObject<BlockTableRecord> )

// Count Blocks in Modelspace and in Associative Arrays;
// 0. Modelspace -> 1. Block
// 0. Modelspace -> 1. AssocArray -> 2. ArrayItem -> 3. Block

[<CommandMethod "CountAssocBlocks">]
let countAssocBlocks() =
    let db = acadApp.DocumentManager.MdiActiveDocument.Database
    let ed = acadApp.DocumentManager.MdiActiveDocument.Editor

    use tr = db.TransactionManager.StartTransaction()
   
    SymbolUtilityServices.GetBlockModelSpaceId db
    |> getNestedBTRs
    |> Seq.collect (fun btr1 ->
        if btr1.IsAnonymous then
            getObjects<BlockReference> btr1
            |> Seq.filter (fun bref2 ->
                (getObject<BlockTableRecord> bref2.BlockTableRecord).IsAnonymous )
            |> Seq.collect (fun bref2 ->
                bref2.BlockTableRecord
                |> getNestedBTRs       
                |> Seq.map (fun btr3 -> btr1.Name, btr3.Name ) )
        else
            Seq.singleton(SymbolUtilityServices.BlockModelSpaceName, btr1.Name) )
    |> Seq.groupBy id
    |> Seq.iter (fun ((ownerName, name), v) ->
        ed.WriteMessage("\n{0} {1} : {2} ", ownerName, name, Seq.length v) )
   
    tr.Commit()

Jeff H

  • Needs a day job
  • Posts: 4632
Re: Counting nested blocks + regular blocks
« Reply #4 on: December 20, 2011, 09:54:46 am »
I have spent no time thinking about this but would something like
Code: [Select]

 GetBlockReferenceIds(true, false).Count  + GetBlockReferenceIds(false, false).Count
give you total count and adding the additional anonymous blocks for dynamic blocks??

kaefer

  • Swamp Rat
  • Posts: 562
Re: Counting nested blocks + regular blocks
« Reply #5 on: December 20, 2011, 10:49:10 am »
I have spent no time thinking about this but would something like
Code: [Select]

 GetBlockReferenceIds(true, false).Count  + GetBlockReferenceIds(false, false).Count
give you total count and adding the additional anonymous blocks for dynamic blocks??

Nope, it would get you the count of direct BlockReferences twice and that of their parents once (that's what I think the doc says).

But otherwise you're right, we can certainly work from the inside out: We would need to check each parent BlockReference and each grandparent BlockReference again and again; which could be deemed inefficient, unless cached.

Jeff H

  • Needs a day job
  • Posts: 4632
Re: Counting nested blocks + regular blocks
« Reply #6 on: December 20, 2011, 10:57:52 am »
Gotcha,
 
Thanks Kaefer

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: Counting nested blocks + regular blocks
« Reply #7 on: December 20, 2011, 05:26:08 pm »
Hi,

Here're two little sample to get all references, whatever their nesting level, of the blocks which names match the supplied wild-card pattern argument.
Both functions return an ObjectIdCollection

"Imperative" style using C#

Code: [Select]
        public static ObjectIdCollection GetAllBlockReferencesByNames(string pattern)
        {
            Database db = HostApplicationServices.WorkingDatabase;
            ObjectIdCollection result = new ObjectIdCollection();
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                foreach (ObjectId id in bt)
                {
                    BlockTableRecord btr = (BlockTableRecord)id.GetObject(OpenMode.ForRead);
                    if (Utils.WcMatch(btr.Name, pattern))
                    {
                        foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false))
                        {
                            BlockTableRecord owner;
                            if (IsPrimary(refId, out owner))
                                result.Add(refId);
                            else
                                GetNestedReferences(refId, owner, ref result);
                        }
                    }
                }
                tr.Commit();
            }
            return result;
        }

        private static void GetNestedReferences(ObjectId refId, BlockTableRecord btr, ref ObjectIdCollection result)
        {
            foreach (ObjectId id in btr.GetBlockReferenceIds(true, false))
            {
                BlockTableRecord owner;
                if (IsPrimary(id, out owner))
                    result.Add(refId);
                else
                    GetNestedReferences(refId, owner, ref result);
            }
        }

        private static bool IsPrimary(ObjectId id, out BlockTableRecord owner)
        {
            BlockReference br = (BlockReference)id.GetObject(OpenMode.ForRead);
            owner = (BlockTableRecord)br.OwnerId.GetObject(OpenMode.ForRead);
            return owner.IsLayout;
        }

"Functional" style using F#

Code: [Select]
let getAllRefs pat =
    let db = HostApplicationServices.WorkingDatabase
    use tr = db.TransactionManager.StartTransaction()

    let getOwner id =
        let br = tr.GetObject(id, OpenMode.ForRead) :?> BlockReference
        tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord

    let rec getNested (refId:ObjectId) (btr:BlockTableRecord) =
        btr.GetBlockReferenceIds(true, false)
        |> Seq.cast<ObjectId>
        |> Seq.collect(fun id ->
            let owner = getOwner id
            if owner.IsLayout then Seq.singleton refId else getNested refId owner)

    new ObjectIdCollection(
        tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
        |> Seq.cast<ObjectId>
        |> Seq.map(fun id -> tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord)
        |> Seq.filter(fun btr -> Utils.WcMatch(btr.Name, pat))
        |> Seq.collect(fun btr ->
            btr.GetBlockReferenceIds(true, false)
            |> Seq.cast<ObjectId>
            |> Seq.collect(fun id ->
                let owner = getOwner id
                if owner.IsLayout then Seq.singleton id else getNested id owner))
        |> Seq.toArray)
Speaking English as a French Frog

kaefer

  • Swamp Rat
  • Posts: 562
Re: Counting nested blocks + regular blocks
« Reply #8 on: December 21, 2011, 05:42:13 am »
Hi,

That works very nicely with nested blocks. But, alas, not with those of the dynamic variety.

Adding the loop over GetAnonymousBlockIds() made a little refactoring necessary; may you excuse my meddling interference. Still left as an exercise for the reader: introduce some kind of grouping based on ownership.

Code: [Select]
        public static ObjectIdCollection GetAllBlockReferencesByNames(string pattern)
        {
            Database db = HostApplicationServices.WorkingDatabase;
            ObjectIdCollection result = new ObjectIdCollection();
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                foreach (ObjectId id in bt)
                {
                    BlockTableRecord btr = (BlockTableRecord)id.GetObject(OpenMode.ForRead);
                    if (Utils.WcMatch(btr.Name, pattern))
                    {
                        foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false))
                        {
                            GetNestedReferences(refId, refId, ref result);
                        }
                        foreach (ObjectId anonId in btr.GetAnonymousBlockIds())
                        {
                            BlockTableRecord anonBTR = (BlockTableRecord)tr.GetObject(anonId, OpenMode.ForRead);
                            foreach (ObjectId refId in anonBTR.GetBlockReferenceIds(true, false))
                            {
                                GetNestedReferences(refId, refId, ref result);
                            }
                        }
                    }
                }
                tr.Commit();
            }
            return result;
        }

        private static void GetNestedReferences(ObjectId curId, ObjectId refId, ref ObjectIdCollection result)
        {
            BlockTableRecord owner;
            if (IsPrimary(curId, out owner))
                result.Add(refId);
            else
                foreach (ObjectId id in owner.GetBlockReferenceIds(true, false))
                    GetNestedReferences(id, refId, ref result);
        }

        private static bool IsPrimary(ObjectId id, out BlockTableRecord owner)
        {
            BlockReference br = (BlockReference)id.GetObject(OpenMode.ForRead);
            owner = (BlockTableRecord)br.OwnerId.GetObject(OpenMode.ForRead);
            return owner.IsLayout;
        }

Firmly beholding the motes in your eye, F# version: The constructor of ObjectIdCollection chokes with IndexOutOfRangeException on an empty array.

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: Counting nested blocks + regular blocks
« Reply #9 on: December 21, 2011, 07:04:24 am »
Nice correction, kaefer.
Thanks again.
Speaking English as a French Frog

kaefer

  • Swamp Rat
  • Posts: 562
Re: Counting nested blocks + regular blocks
« Reply #10 on: December 21, 2011, 10:18:09 am »
Thanks again.

Thanks too!

I'll have to vindicate Jeff H now: grouping by owner is almost an afterthought when a minor change is made to gile's function: do not return an ObjectIdCollection but lists of ObjectId equivalent to FullSubentityPaths instead. These can be used to check the nesting level and even for access to the ItemLocators, if desired.

For now, it's only the F# function, modified for Dynamic Blocks and with a sequence expression to ensure tail recursion.

Code: [Select]
// inspired by gile @ theswamp.org,
// http://www.theswamp.org/index.php?topic=40440.msg457662#msg457662

// Returns an array of ObjectId list - each list contains the ObjectIds
// necessary to construct a FullSubentityPath for each BlockReference

let groupRefsByOwner pat =
    let db = HostApplicationServices.WorkingDatabase
    use tr = db.TransactionManager.StartTransaction()

    let getOwner id =
        let br = tr.GetObject(id, OpenMode.ForRead) :?> BlockReference
        tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord

    let rec getNested containerIds (btr:BlockTableRecord) =
        let directRefIds =
            btr.GetBlockReferenceIds(true, false)
            |> Seq.cast<ObjectId>
        let anonymousBTRs =
            btr.GetAnonymousBlockIds()
            |> Seq.cast<ObjectId>
            |> Seq.map(fun oid -> tr.GetObject(oid, OpenMode.ForRead) :?> BlockTableRecord)
        seq{
            for oid in directRefIds do
                let owner, res = getOwner oid, oid :: containerIds
                if owner.IsLayout then yield res else yield! getNested res owner
            for btr in anonymousBTRs do
                yield! getNested containerIds btr
        }

    let result =
        tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
        |> Seq.cast<ObjectId>
        |> Seq.map(fun oid -> tr.GetObject(oid, OpenMode.ForRead) :?> BlockTableRecord)
        |> Seq.filter(fun btr -> Utils.WcMatch(btr.Name, pat))
        |> Seq.collect(getNested [])
        |> Seq.toArray
    tr.Commit()
    result

The calling site could be like this:

Code: [Select]
        groupRefsByOwner pr.StringResult
        |> Seq.groupBy (function
            | outerAnonRefId::_::_::[] -> Some[ outerAnonRefId ] // third level of nesting
            | _::[] -> Some[]                                    // first level of nesting
            | _ -> None )
        |> Seq.iter (fun (grp, oids) ->
            let cnt = Seq.length oids
            match grp with
            | Some [] -> ed.WriteMessage("\nIn Layout: {0} ", cnt)
            | Some(outerAnonRefId::[]) -> ed.WriteMessage("\n{0}: {1} ", outerAnonRefId, cnt )
            | _ -> ed.WriteMessage("\nOtherwise nested: {0} ", cnt) )

Edit: typos fixed.
« Last Edit: December 21, 2011, 03:26:01 pm by kaefer »

gile

  • Water Moccasin
  • Posts: 1736
  • Marseille, France
Re: Counting nested blocks + regular blocks
« Reply #11 on: December 21, 2011, 02:32:51 pm »
Very very nice kaefer.
I really like the way you code. :kewl:
Speaking English as a French Frog

Jeff H

  • Needs a day job
  • Posts: 4632
Re: Counting nested blocks + regular blocks
« Reply #12 on: January 17, 2012, 09:42:34 pm »
Assuming you do not want to count the blockreferences that are in BlockDefinitions but all that are 'inserted' directly or 'inserted' through nesting however many times deep;
Wouldn't getting GetBlockReferenceIds(false, false) then removing each ObjectId where the blockReference OwnerId or BlockId is not a layout give the correct count?
 
If you wanted the count just for ones inserted directly and not count ones that are 'inserted' in a nested block then changing the first argument to true for GetBlockReferenceIds(true, false), but would still need to remove the blockReference ObjectId if it's OwnerId or BlockId is not a layout

Eycki

  • Newt
  • Posts: 41
Re: Counting nested blocks + regular blocks
« Reply #13 on: January 25, 2012, 05:34:50 am »
Thanks guys!  works like a charm now!

Too reply on Jeff H:  the blocks I want to count can be both blockreferences in the drawing or nested inside anonymous blocks.  However they will be never nested 2 levels deep.