Thursday, July 10, 2014

Determine Whether 2 Attributes of A BlockReference Overlap

Using Attributes in a block to present textual information is a convenient way in AutoCAD. However, it is often that because of the visual limitation of a block, we expect the Attribute's value (text string) not to be too long to exceed the block's visual boundary.

For example, I have a table-like block, and each row host 2 attributes. The left attribute is left-justified, while the right attribute is right-justified. When setting the attributes' value, it is possible that the 2 attributes get overlapped because of the attributes' value text string is too long. If the attributes are updated by user manually, the user obviously can see what happens and can handle it as needed, for example, change the text value of the attributes to try to fit the allowed space. However, when the attributes are to be updated by code, as programmer, we may want to know if the 2 attributes would overlap when certain attribute values are set.

I ran into an similar situation. In this case, if I can test the 2 target attributes would be overlap with given attribute values, the code should change the block's dynamic property to avoid the overlap.

So, I wrote some code to see how easy (or, difficult) to test if 2 attributes are overlap. In my case, to make things easier, the attribute in the block is not MText type. So, it is just a single line of text. Therefore, I only need to find out each attribute's bounding box (GeometricExtents) and whether one of the corner points of entents A is inside entents B, then the 2 extents are overlap. Of course, this only applies to attributes with rotation angle equal to 0. if the block has to be inserted into certain rotation angle, I can do this overlapping test before the block is rotated.

Here is the code:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(AttributeOverlapTest.MyCadCommands))]
 
namespace AttributeOverlapTest
{
    public class MyCadCommands
    {
        [CommandMethod("AttOverlap")]
        public static void TestAttributeOverlap()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            try
            {
                ObjectId attrId1 = PickAttribute(ed);
                if (!attrId1.IsNull)
                {
                    ObjectId attrId2 = PickAttribute(ed);
                    if (!attrId2.IsNull)
                    {
                        bool overlap = TestAttributeOverlap(
                            dwg.Database, attrId1, attrId2);
                        ed.WriteMessage("\n{0}.", 
                            overlap ? "Overlapped" : "Not overlapped");
                    }
                }
                
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError: {0}\n{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
 
        private static ObjectId PickAttribute(Editor ed)
        {
            ObjectId id = ObjectId.Null;
 
            while (true)
            {
                PromptNestedEntityOptions opt =
                    new PromptNestedEntityOptions(
                        "\nSelect an attribute in a block:");
 
                PromptNestedEntityResult res = ed.GetNestedEntity(opt);
                if (res.Status == PromptStatus.OK)
                {
                    if (res.ObjectId.ObjectClass.DxfName.ToUpper() == "ATTRIB")
                    {
                        id = res.ObjectId;
                        break;
                    }
                    else
                    {
                        ed.WriteMessage(
                            "\nInvalid selection: not an attribute.");
                    }
                }
                else
                {
                    break;
                }
            }
 
            return id;
        }
 
        private static bool TestAttributeOverlap(
            Database db, ObjectId attId1, ObjectId attId2)
        {
            Extents3d ext1;
            Extents3d ext2;
            using (Transaction tran=db.TransactionManager.StartTransaction())
            {
                Entity ent1 = (Entity)tran.GetObject(attId1, OpenMode.ForRead);
                Entity ent2 = (Entity)tran.GetObject(attId2, OpenMode.ForRead);
                ext1 = ent1.GeometricExtents;
                ext2 = ent2.GeometricExtents;
                tran.Commit();
            }
 
            if (ExtentsOverlap(ext1, ext2)) return true;
 
            return false;
        }
 
        private static bool ExtentsOverlap(Extents3d ext1, Extents3d ext2)
        {
            //Get 4 points from one of the extents
            Point3d[] pts = new Point3d[]
            {
                new Point3d(ext2.MinPoint.X, ext2.MinPoint.Y, 0.0),
                new Point3d(ext2.MaxPoint.Y, ext2.MaxPoint.Y, 0.0),
                new Point3d(ext2.MinPoint.X, ext2.MaxPoint.Y, 0.0),
                new Point3d(ext2.MaxPoint.X, ext2.MinPoint.Y, 0.0)
            };
 
            //Add each point into the other extents, 
            //if the extents is not changed,
            //that means the 2 extents are overlapped in 2D plane
            foreach (var p in pts)
            {
                Extents3d ext = new Extents3d(
                    new Point3d(ext1.MinPoint.X, ext1.MinPoint.Y, 0.0),
                    new Point3d(ext1.MaxPoint.X, ext1.MaxPoint.Y, 0.0));
 
                ext.AddPoint(p);
 
                if (ext == ext1) return true;
            }
 
            return false;
        }
    }
}

Go to this video clip to see how the code runs.

With this piece of code, I can update one of my existing application to avoid overlapped attributes when updating the attribute values programmatically.



No comments:

Post a Comment