Question

I have objects in Autocad drawing with property named Base. I am trying to find all objects in that drawing with Base property has a specific string value such as "Pipe".

I can iterate objects in the drawing and get all object ids. Then I get all properties of object with that Id and check if property named Base = "Pipe".

Iteration performance is not good enough. Is there any way to directly get object ids that has property named Base = "Pipe"?

Here is how I iterate through all objects:

    List<ObjectId> ObjectIds = new List<ObjectId>();

    foreach (Document Document in Documents)
    {
        Database Database = Document.Database;

        using (Transaction Transaction = Database.TransactionManager.StartTransaction())
        {
            for (long i = Database.BlockTableId.Handle.Value; i < Database.Handseed.Value; i++)
            {
                ObjectId Id;

                if (Database.TryGetObjectId(new Handle(i), out Id))
                {
                        ObjectIds.Add(Id);
                }
            }

            Transaction.Commit();
        }
    }

And here is how I get all properties of the objects in my ObjectIds collection.

public static DataLinksManager DataLinks
{
    get
    {
        if (null == _DataLinks)
        {
            StringCollection Coll = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetLinkManagerNames();

            if (Coll.Count > 0)
            {
                if (Coll[0] != string.Empty)
                {
                    _DataLinks = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetManager(Coll[0]);
                }
            }
        }

        return _DataLinks;
    }
}

private static DataLinksManager _DataLinks;

foreach(var Id in ObjectIds)
{
    List<KeyValuePair<string, string>> Properties = DataLinks.GetAllProperties(Id, true);
    // I check existence of my property and if so its value.
}
Was it helpful?

Solution

In case anyone needs, here is the code that is solution to my problem. The trick is the Iterate method. This is based on this article by Philippe Leefsma. What I add to this method is a list of ObjectClass properties of found ObjectId instances. My sample dawing has around 8500 ObjectIds. However what I'm interested in is objects with base classes acppasset and acppdynamicasset and count of such objects is 90.

using Autodesk.AutoCAD.ApplicationServices;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;

public static void GetObjects()
{
    List<KeyValuePair<string, ObjectId>> ObjectIds = new List<KeyValuePair<string, ObjectId>>();

    List<string> Filter = new List<string>() { "acppasset", "acppdynamicasset" };

    foreach (Document Document in this.Documents)
    {
        ObjectIdCollection Ids = this.Iterate(Document, Filter);
        if (null != Ids) foreach (var Id in Ids.OfType<ObjectId>()) ObjectIds.Add(new KeyValuePair<string, ObjectId>(System.IO.Path.GetFileNameWithoutExtension(Document.Name), Id));
    }

    this.Results = new Dictionary<string, List<List<KeyValuePair<string, string>>>>();

    foreach (var Id in ObjectIds)
    {
        try
        {
            var Properties = this.GetObject(Id.Value);
            if (null == Properties) continue;

            var Base = Properties.Where(x => x.Key == "Base").FirstOrDefault();

            if (string.IsNullOrWhiteSpace(Base.Value)) continue;

            if (!this.Results.ContainsKey(Base.Value)) this.Results.Add(Base.Value, new List<List<KeyValuePair<string, string>>>());

            this.Results[Base.Value].Add(Properties);
        }   catch { }
    }
}

 public ObservableCollection<Document> Documents { get; set; }

 public ObjectIdCollection Iterate(Document Document, List<string> Filter = null)
        {
            ads_name Instance = new ads_name();
            Database Database = Document.Database;

            ObjectIdCollection ValidIds = new ObjectIdCollection();

            // Get the last handle in the Database
            Handle Handseed = Database.Handseed;

            // Copy the handseed total into an efficient raw datatype
            long HandseedTotal = Handseed.Value;

            for (long i = 1; i < HandseedTotal; ++i)
            {
                string Handle = Convert.ToString(i, 16);

                int Result = acdbHandEnt(Handle, ref Instance);
                if (Result != 5100) continue; // RTNORM

                ObjectId Id = new ObjectId(Instance.a);

                if (!Id.IsValid) continue;

                try
                {
                    if (null != Filter)
                    {
                        if (!Filter.Contains(Id.ObjectClass.Name.ToLower())) continue;
                    }

                    using (DBObject DBObject = Id.Open(OpenMode.ForRead, false))
                    {
                        ValidIds.Add(Id);
                        DBObject.Dispose();
                    }
                }   catch { }
            }

            return ValidIds;
    }

    public List<KeyValuePair<string, string>> GetObject(ObjectId Id)
    {
        if (Command.DataLinks != null) try { return Command.DataLinks.GetAllProperties(Id, true); } catch { return null; }
        return null;
    }

public static DataLinksManager DataLinks
{
    get
    {
        if (null == _DataLinks)
        {
            StringCollection Coll = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetLinkManagerNames();

            if (Coll.Count > 0)
            {
                if (Coll[0] != string.Empty)
                {
                    _DataLinks = Autodesk.ProcessPower.DataLinks.DataLinksManager.GetManager(Coll[0]);
                }
            }
        }

            return _DataLinks;
        }
    }

private static DataLinksManager _DataLinks;

public Dictionary<string, List<List<KeyValuePair<string, string>>>> Results { get; set; }

OTHER TIPS

The slow performance here is because it attempts to read all the objects and check if it contains any attribute. As far as I know, the attributes exist only for block references(inserts). So if selection filters are used, we could get direct access to only those records based on the filter criteria.

I found a pretty easy example here using selection filter that selects all blocks with a particular name.

Copying a part of that code for reference. This selects only the block references. You can iterate from here.

TypedValue[] filterlist = new TypedValue[1];
filterlist[0] = new TypedValue(0, "INSERT");
SelectionFilter filter =  new SelectionFilter(filterlist);

PromptSelectionResult selRes =  ed.SelectAll(filter);

if (selRes.Value.Count != 0)
{
    SelectionSet set = selRes.Value;

    foreach (ObjectId id in set.GetObjectIds())
    {
        BlockReference oEnt = (BlockReference)tr.GetObject(id, OpenMode.ForWrite);
        //do something with oEnt..;
    }

}

If you can add complexities to your filter, you will need to iterate only a very small set.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top