TheSwamp
Code Red => .NET => Topic started by: Eycki 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.
-
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.
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.
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;
}
}
-
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.
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.
-
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.
// 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()
-
I have spent no time thinking about this but would something like
GetBlockReferenceIds(true, false).Count + GetBlockReferenceIds(false, false).Count
give you total count and adding the additional anonymous blocks for dynamic blocks??
-
I have spent no time thinking about this but would something like
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.
-
Gotcha,
Thanks Kaefer
-
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#
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#
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)
-
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.
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.
-
Nice correction, kaefer.
Thanks again.
-
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.
// 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:
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.
-
Very very nice kaefer.
I really like the way you code. :kewl:
-
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
-
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.