speed problem with modifying properties of multiple dynamic blocks

genosyde
Advocate
Advocate

speed problem with modifying properties of multiple dynamic blocks

genosyde
Advocate
Advocate

Hello. Developers.
I'm currently developing an add-in that inserts dynamic blocks.


The function of the button is to define an attribute in a dynamic block and enter a text value.
Then change the property value for Dimension length.

 

The number of dynamic blocks to be inserted in this way is 3000 or more.
I'm having an issue with it being constantly slow after the first trigger button is clicked.
I looked up related issues on Stack Overflow, AutoCAD forums, and the swamp.
However, I can't find a way to solve this problem, so I'm posting this.
This add-in is almost complete,
If you can't help me, I'm in a hell  where I have to redesign logic.
Please help.

 

Below is the code.
I used Windows 10 X64, .NET Framework 4.7.

using (var br = new BlockReference(insPt, bt[blockName]))
{
    br.Layer = layerName;
    double degree = Convert.ToInt32(angle * 180 / Math.PI);
    br.Rotation = angle;
    double tmpangle = angle;
    if(angle > 2 * Math.PI)
    {
        tmpangle = angle % (2 * Math.PI);
    }

    if (tmpangle >= Math.PI / 2 && tmpangle <= Math.PI || tmpangle >= Math.PI && tmpangle <= (3 * Math.PI) / 2)
        br.TransformBy(Matrix3d.Rotation(Math.PI, br.Normal, cpt));


    var space = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
    space.AppendEntity(br);
    tr.AddNewlyCreatedDBObject(br, true);

    if (attrList.Length > 0)
    {
        //  Add Attribute Define
        DBObject dbObj;
        AttributeDefinition acAtt;
        if (btr.HasAttributeDefinitions)
        {
            foreach (ObjectId objID in btr)
            {
                dbObj = tr.GetObject(objID, OpenMode.ForRead) as DBObject;
                if (dbObj is AttributeDefinition)
                {
                    acAtt = dbObj as AttributeDefinition;
                    //if (!acAtt.Constant && attrList.Contains(acAtt.Tag.ToUpper()))
                    if (!acAtt.Constant && acAtt.Tag.ToUpper() == "PARAMETER")
                    {
                        using (AttributeReference acAttRef = new AttributeReference())
                        {
                            acAttRef.SetAttributeFromBlock(acAtt, br.BlockTransform);
                            acAttRef.Position = acAtt.Position.TransformBy(br.BlockTransform);
                            acAttRef.TextString = parameter;

                            var arId = br.AttributeCollection.AppendAttribute(acAttRef);
                        }
                    }
                }
            }
        }
    }


    //  Set dynamic block reference Property
    if (!br.IsDynamicBlock) { ed.WriteMessage("it's not dynamic block reference"); }
    else
    {
        foreach (DynamicBlockReferenceProperty prop in br.DynamicBlockReferencePropertyCollection)
        {
            if (prop == null || !templetNames.Contains(prop.PropertyName)) continue;

            int propidx = Array.IndexOf(templetNames, prop.PropertyName);
            if (prop.PropertyTypeCode == 5)
            {
                prop.Value = templetValues[propidx];
            }
            else if (prop.PropertyTypeCode == 2)
            {
                int tmpInt = 0;
                int.TryParse(templetValues[propidx], out tmpInt);
                prop.Value = tmpInt;
            }
            else if (prop.PropertyTypeCode == 1)
            {
                double tmpDouble = 0;
                double.TryParse(templetValues[propidx], out tmpDouble);
                prop.Value = tmpDouble;
            }
        }
    }

    //  XData 
    FlipData fdata = new FlipData();
    fdata.appname = "FLIP";
    fdata.direction = sptMoved ? "REVERSED" : "RIGHT";
    fdata.idx = idx;
    fdata.elemID = elemID;
    fdata.length = unitLen;
    fdata.spt = br.Position.ToString();
    fdata.ept = Point3d.Origin.ToString();
    fdata.rotation = br.Rotation;
    fdata.tmpletBtrName = tmpletBtrName;
    fdata.sptMoved = sptMoved ? "Y" : "N";
    fdata.isFliped = "N";
    CADUtil.SetXData(br.Id, fdata);


    br.RecordGraphicsModified(true);
    brID = br.Id;
}
0 Likes
Reply
Accepted solutions (1)
678 Views
7 Replies
Replies (7)

norman.yuan
Mentor
Mentor
Accepted solution

It is known issue that excessive use of dynamic block  would slow down AutoCAD significantly. 3000+ would definitely be considered excessive. There is not much we can do, except for try not to use that many dynamic blocks with most their dynamic properties having different values, which causes AutoCAD being so busy to figure out if it needs to create an anonymous block definition or not.

 

Norman Yuan

Drive CAD With Code

EESignature

genosyde
Advocate
Advocate

norman.yuan thanks for your guidance.
Thanks to your comments, I learned a little bit more about dynamic and anonymous blocks.

 

"we can do, except for try not to use that many dynamic blocks with most their dynamic properties having different values"

 

I think this is the most important thing.
My add-in often has duplicated property values.
So, I am trying to modify my logic by finding and inserting dynamic block references with duplicated values ​​into the previously created anonymous block.

 

Whenever I create a new anonymous block, I will put the value and id in the DB
When creating the next dynamic block, I will first find out if there is the same value in the DB and clone the previously created anonymous block of id with the clone() function.

 

If you do this, the Transform's Location value needs to be refreshed.
At this time, we need to check whether AutoCad creates a new anonymous block. You said that an anonymous block is created when the value of the property is changed...

sorry. I still don't know exactly how anonymous blocks are created. I hope this work will help you learn a little more about the principle of anonymous blocks.

 

We will post more detailed information after testing. thank you.

0 Likes

norman.yuan
Mentor
Mentor

@genosyde wrote:

...
Whenever I create a new anonymous block, I will put the value and id in the DB
When creating the next dynamic block, I will first find out if there is the same value in the DB and clone the previously created anonymous block of id with the clone() function.

...


 I would definitely not try to clone the anonymous blocks created by AutoCAD for its dynamic block. Each anonymous block created by AutoCAD for the dynamic block has specific data attached besides its geometric entities, which would associate the anonymous block definition with the dynamic block definition in some ways. These data are not exposed via API. So, how do you handle/manipulate the data in your cloned anonymous block?

Norman Yuan

Drive CAD With Code

EESignature

genosyde
Advocate
Advocate

Dear norman.yuan
What you said was right.


"I would definitely not try to clone the anonymous blocks created by AutoCAD for its dynamic block.
AutoCAD for the dynamic block has specific data attached besides its geometric entities, which would associate the anonymous block definition with the dynamic block definition in some ways."

 

I copied the dynamic block reference from the UI of AutoCAD and tried to increase it to more than 1000.
Even copying dynamic block references is consistently slow in some way.
Even if you use the Clone() of the API, it seems to affect the slowness invariably.

So I'm thinking of changing the design.
Currently my logic is inputting one dynamic block reference and then
"dimregen"
"regen"
"attsync"
to update the screen state.
I repeat this process over 3000 times.


In addition to this, I plan to add a task to turn dynamic blocks into static blocks.
That means breaking dynamic blocks.

So I found something called UnDynamic.lsp from the link below.
The Swap UnDynamic 

I changed it to a static block on the AutoCAD UI and performed a copy test.
I'm sure it's fast.

 

I will write and run the test code in this way and post a comment again.

Thanks alot.

0 Likes

genosyde
Advocate
Advocate

The test results still have the problem of being slow.
But I really don't know.
The problematic part is BlockTableRecord.updateanonymousBlocks(), which replaces the command "Attsync".

As mentioned in the comment above, by putting one dynamic block and executing Explode_Dyanablock() after "Attsync", "Dimregen", "Regen"
Fixed a loop that changes dynamic blocks to static blocks.

The problematic updateanonymousBlocks() was initially less than 0.5 seconds.
I've run 270 and now it's over 4 seconds.

Below is the modified code. What am I missing?

 

using (var br = new BlockReference(insPt, bt[blockName]))
{
    br.Layer = layerName;
    double degree = Convert.ToInt32(angle * 180 / Math.PI);
    br.Rotation = angle;
    double tmpangle = angle;
    if(angle > 2 * Math.PI)
    {
        tmpangle = angle % (2 * Math.PI);
    }

    if (tmpangle >= Math.PI / 2 && tmpangle <= Math.PI || tmpangle >= Math.PI && tmpangle <= (3 * Math.PI) / 2)
        br.TransformBy(Matrix3d.Rotation(Math.PI, br.Normal, cpt));

    var space = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
    space.AppendEntity(br);
    tr.AddNewlyCreatedDBObject(br, true);

    if (attrList.Length > 0)
    {
        //  add attribute define
        DBObject dbObj;
        AttributeDefinition acAtt;
        if (btr.HasAttributeDefinitions)
        {
            foreach (ObjectId objID in btr)
            {
                dbObj = tr.GetObject(objID, OpenMode.ForRead) as DBObject;
                if (dbObj is AttributeDefinition)
                {
                    acAtt = dbObj as AttributeDefinition;
                    //if (!acAtt.Constant && attrList.Contains(acAtt.Tag.ToUpper()))
                    if (!acAtt.Constant && acAtt.Tag.ToUpper() == "PARAMETER")
                    {
                        using (AttributeReference acAttRef = new AttributeReference())
                        {
                            acAttRef.SetAttributeFromBlock(acAtt, br.BlockTransform);
                            acAttRef.Position = acAtt.Position.TransformBy(br.BlockTransform);
                            acAttRef.TextString = parameter;
                            var arId = br.AttributeCollection.AppendAttribute(acAttRef);
                        }
                    }
                }
            }
        }
    }


    //  Set Property
    if (!br.IsDynamicBlock) { ed.WriteMessage("다이나믹 블럭이 아닙니다."); }
    else
    {
        if (br.IsWriteEnabled)
        {
            br.UpgradeOpen();
            foreach (DynamicBlockReferenceProperty prop in br.DynamicBlockReferencePropertyCollection)
            {
                if (prop == null || !templetNames.Contains(prop.PropertyName)) continue;

                int propidx = Array.IndexOf(templetNames, prop.PropertyName);
                if (prop.PropertyTypeCode == 5)
                {
                    prop.Value = templetValues[propidx];
                }
                else if (prop.PropertyTypeCode == 2)
                {
                    int tmpInt = 0;
                    int.TryParse(templetValues[propidx], out tmpInt);
                    prop.Value = tmpInt;
                }
                else if (prop.PropertyTypeCode == 1)
                {
                    double tmpDouble = 0;
                    double.TryParse(templetValues[propidx], out tmpDouble);
                    prop.Value = tmpDouble;
                }
            }
            br.DowngradeOpen();
        }
    }

    //  XData 기입
    FlipData fdata = new FlipData();
    fdata.appname = "FLIP";
    fdata.direction = sptMoved ? "REVERSED" : "RIGHT";
    fdata.idx = idx;
    fdata.elemID = elemID;
    fdata.length = unitLen;
    fdata.spt = br.Position.ToString();
    fdata.ept = Point3d.Origin.ToString();
    fdata.rotation = br.Rotation;
    fdata.tmpletBtrName = tmpletBtrName;
    fdata.sptMoved = sptMoved ? "Y" : "N";
    fdata.isFliped = "N";
    CADUtil.SetXData(br.Id, fdata);
    brID = br.Id;
}

BlockReference postBr = tr.GetObject(brID, OpenMode.ForWrite, false, true) as BlockReference;
acap.UpdateScreen();
postBr.RecordGraphicsModified(true);
ObjectId dynBtrDefId = postBr.DynamicBlockTableRecord;
if (!dynBtrDefId.IsNull)
{
    var def = tr.GetObject(dynBtrDefId, OpenMode.ForRead) as BlockTableRecord;
    //   replace cmd "Attsync"
    def.UpdateAnonymousBlocks();
    //  DIMREGEN
    object ActiveDocument = doc.GetAcadDocument();
    object[] data = { "_.DIMREGEN\n" };
    ActiveDocument.GetType().InvokeMember("SendCommand",
                                            System.Reflection.BindingFlags.InvokeMethod,
                                            null, ActiveDocument, data
                                            );
    //  REGEN
    object[] data2 = { "_.REGEN\n" };
    ActiveDocument.GetType().InvokeMember("SendCommand",
                        System.Reflection.BindingFlags.InvokeMethod,
                        null, ActiveDocument, data2
                        );

    tr.TransactionManager.QueueForGraphicsFlush();
    //  dynamic block -> static block
    explode_dynablocks(db);

}

tr.Commit();

 

 

method : expolde_dynablocks

 

public static int explode_dynablocks(Database db)
{
    int result = 0;

    Transaction tr = db.TransactionManager.StartTransaction();

    using (tr)
    {
        BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

        foreach (ObjectId objId in bt)
        {
            BlockTableRecord ent = (BlockTableRecord)tr.GetObject(objId, OpenMode.ForRead);
            if (!ent.IsErased && ent.IsDynamicBlock)
            {
                ObjectIdCollection oic = ent.GetBlockReferenceIds(false, false);
                ObjectIdCollection oid = ent.GetAnonymousBlockIds();
                                                                        
                foreach (ObjectId o in oid)
                {
                    DBObject dbo = tr.GetObject(o, OpenMode.ForRead);
                    if (dbo is BlockTableRecord)
                    {
                        BlockTableRecord abt = (BlockTableRecord)dbo;
                        ObjectIdCollection oc = abt.GetBlockReferenceIds(false, false);
                        if (oc != null)
                            foreach (ObjectId id in oc) oic.Add(id);
                    }
                    else if (dbo is BlockReference)
                    {
                        oic.Add(o);
                    }
                }

                foreach (ObjectId o in oic)
                {
                    DBObject dbo = tr.GetObject(o, OpenMode.ForRead, false);
                    if ((dbo is BlockReference) && (!dbo.IsErased))
                    {
                        BlockReference br = (BlockReference)dbo;

                        if (br.IsDynamicBlock)
                        {
                            // convert dynamic block into anonymous
                            br.UpgradeOpen();
                            br.ConvertToStaticBlock();
                            result++;
                        }

                        // converted blocks have now a new BTR
                        // cycle through it and erase invisible entities
                        BlockTableRecord btr = (BlockTableRecord)tr.GetObject(br.IsDynamicBlock 
                            ? br.DynamicBlockTableRecord : br.BlockTableRecord, OpenMode.ForRead);

                        foreach (ObjectId oin in btr)
                        {
                            DBObject dboin = tr.GetObject(oin, OpenMode.ForRead, false);
                            if ((dboin is Entity) && (!dboin.IsErased))
                            {
                                Entity ee = dboin as Entity;
                                if (!ee.Visible)
                                {
                                    ee.UpgradeOpen();
                                    ee.Erase(true);
                                }
                            }
                        }
                    }
                }
            }
        }

        tr.Commit();
    }

    return result;
}

 

 

0 Likes

genosyde
Advocate
Advocate

Now I add more code about deleting anonymous block reference.

so, it was good.

at the previous test, i got 4 seconds over 200 times,

but now i got 1.478 seconds over 706 times.

 

still, i have problem increasing process time.

because, when i start current test, i got 0.85 seconds.

 

please, some one can tell me about this increments.

why?

0 Likes

genosyde
Advocate
Advocate

I found the solution to the problem.
Erase anonymous blocks with the Purge command
A side effect of inserting new blocks is that the time increase rate is very slow.
The time growth rate measurements were as follows:


Loop
200 times    1.171 sec
780 times    1.8sec
1297times    2.8sec
1379times    2.926sec

 

I don't know why dynamic block editing doesn't automatically delete anonymous blocks that are garbage.
Hope this helps other developers in the future.
I will also upload the fuzzy source code.

 

object ActiveDocument = doc.GetAcadDocument();
//   Attsync와 같음
//def.UpdateAnonymousBlocks();
object[] data0 = { "_.ATTSYNC\n" + "_name\n" + tmpletBtrName + "\n" };
ActiveDocument.GetType().InvokeMember("SendCommand",
                                        System.Reflection.BindingFlags.InvokeMethod,
                                        null, ActiveDocument, data0
                                        );
postBr.RecordGraphicsModified(true);
//  DIMREGEN
object[] data1 = { "_.DIMREGEN\n" };
ActiveDocument.GetType().InvokeMember("SendCommand",
                                        System.Reflection.BindingFlags.InvokeMethod,
                                        null, ActiveDocument, data1
                                        );
//  REGEN
object[] data2 = { "_.REGEN\n" };
ActiveDocument.GetType().InvokeMember("SendCommand",
                    System.Reflection.BindingFlags.InvokeMethod,
                    null, ActiveDocument, data2
                    );

tr.TransactionManager.QueueForGraphicsFlush();
//  동적블럭 -> 정적 블럭 변환
explode_dynablocks(db);
//  PURGE
//object[] data3 = { "_.PurgeUnreferencedLayers\n" };
//ActiveDocument.GetType().InvokeMember("SendCommand",
//                  System.Reflection.BindingFlags.InvokeMethod,
//                  null, ActiveDocument, data3
//                  );
object[] data3 = { "_.-PURGE\n"+"B\n"+"*\n"+"N\n" };
ActiveDocument.GetType().InvokeMember("SendCommand",
                    System.Reflection.BindingFlags.InvokeMethod,
                    null, ActiveDocument, data3
                    );

Thanks to all the developers for their help. 

0 Likes