Using Matrix.Mirroring, Attributes are not following MIRRTEXT = 0

Using Matrix.Mirroring, Attributes are not following MIRRTEXT = 0

dennis
Advisor Advisor
1,750 Views
9 Replies
Message 1 of 10

Using Matrix.Mirroring, Attributes are not following MIRRTEXT = 0

dennis
Advisor
Advisor
                acDb.UsingTransaction(tr =>
                {
                    var bt = (BlockTable)tr.GetObject(acDb.BlockTableId, OpenMode.ForRead);
                    var btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
                    foreach (ObjectId id in runids)
                    {
                        var ent = acDb.FetchEntity(id);
                        var clone = (BlockReference)ent.Clone();
                        // discovered that the API mirror doesn't recognize MIRRTEXT to keep the text right-reading
                        clone.TransformBy(Matrix3d.Mirroring(new Line3d(mirrorpt0, mirrorpt1)));
                        //acEd.Command("_MIRROR", id, "", mirrorpt0, mirrorpt1, "N");
                        clone.RecordGraphicsModified(true);
                        var attcol = clone.AttributeCollection;
                        foreach (AttributeReference attref in attcol)
                        {
                            attref.RecordGraphicsModified(true);
                        }
                        btr.AppendEntity(clone);
                        tr.AddNewlyCreatedDBObject(clone, true);
                        cloneids.Add(clone.ObjectId);
                    }
                });

I am using the code above and have found (as my comments say) that the Attributes are not going along with the MIRRTEXT setting == 0;  You can see that I have tried as well using Editor.Command which works just like it should.

I have added in testing RecordGraphicsModified(true) for the clone as well as its attributes.  But I cannot get this to work right.

 

Any one have an idea about what I am missing?

1,751 Views
9 Replies
Replies (9)
Message 2 of 10

norman.yuan
Mentor
Mentor

Cloned entities are not database residing. Your code shows that you do understand it by adding the coned blockreference to the ModelSpace and add to the transaction.

 

However, I think you forgot to also add cloned Attributereferences to the transaction (note, because of cloning, those AttributeReferences should have already included in the cloned BlockReference's AttributeCollection), thus those AttributeReferences are lost when the transaction us commited.

 

Also, are you sure the code:

                        var attcol = clone.AttributeCollection;
                        foreach (AttributeReference attref in attcol)
                        {
                            attref.RecordGraphicsModified(true);
                        }

does not raise exception? Because BlockReference.AttributeCollection contains ObjectId, not AttributeReference. The line of code inside foreach() {....}  should have raise error: you need to open the AttributeReference with Transaction before calling Entity.RecordGraphicsModified().

 

BTW, I really do not see the need to call DBObject.RecordGraphicsModified() on each new Entity, while it may be harmless here.

Norman Yuan

Drive CAD With Code

EESignature

Message 3 of 10

norman.yuan
Mentor
Mentor

Another thought:

 

Since the "foreach(){....}" code did not raise error, it prompts me that the cloned BlockReference's AttributeCollection is actually empty. That is, 

 

BlockReference.Clone() method only generate a clone of BlockReference, but NOT AttributeReferences owned by this Blockreferences. So, you actually need to clone each AttributeRefernece from the original blockreference and then append these cloned attributereferences to the cloned blockreference's attributecollection and add them to the transaction.

 

Norman Yuan

Drive CAD With Code

EESignature

Message 4 of 10

_gile
Consultant
Consultant

Hi,

 

It seems to me you have to rotate the attributes. You can see this topic.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 10

_gile
Consultant
Consultant

Using the following extension methods inspired from the linked AutoCAD DevBlog post seems to work fine.

    // inspired by: https://adndevblog.typepad.com/autocad/2013/10/mirroring-a-dbtext-entity.html
    public static class Extension
    {
        /// <summary>
        /// Gets the points of the text bounding box.
        /// </summary>
        /// <param name="dbText">Instance of DBText the method applies to.</param>
        /// <returns>The bounding box points in counterclockwise sense.</returns>
        public static Point3d[] GetTextBoxCorners(this DBText dbText)
        {
            if (dbText == null)
                throw new ArgumentNullException("dbText");

            var name = new ads_name();
            int result = acdbGetAdsName(ref name, dbText.ObjectId);

            var rb = new ResultBuffer();
            Interop.AttachUnmanagedObject(rb, acdbEntGet(ref name), true);

            var point1 = new double[3];
            var point2 = new double[3];

            acedTextBox(rb.UnmanagedObject, point1, point2);

            var xform =
                Matrix3d.Displacement(dbText.Position.GetAsVector()) *
                Matrix3d.Rotation(dbText.Rotation, dbText.Normal, Point3d.Origin) *
                Matrix3d.WorldToPlane(new Plane(Point3d.Origin, dbText.Normal));

            return new[]
            {
                new Point3d(point1).TransformBy(xform),
                new Point3d(point2[0], point1[1], 0.0).TransformBy(xform),
                new Point3d(point2).TransformBy(xform),
                new Point3d(point1[0], point2[1], 0.0).TransformBy(xform)
            };
        }

        /// <summary>
        /// Mirrors the block reference.
        /// </summary>
        /// <param name="source">Instance of BlockReference the method applies to.</param>
        /// <param name="axis">Mirror axis line.</param>
        /// <param name="eraseSource">Value indicating if the source object have to be erased</param>
        public static void Mirror(this BlockReference source, Line3d axis, bool eraseSource)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (axis == null)
                throw new ArgumentNullException("axis");

            var db = source.Database;
            var tr = db.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            BlockReference mirrored;
            if (eraseSource)
            {
                mirrored = source;
                if (!mirrored.IsWriteEnabled)
                {
                    tr.GetObject(mirrored.ObjectId, OpenMode.ForWrite);
                }
            }
            else
            {
                var ids = new ObjectIdCollection(new[] { source.ObjectId });
                var mapping = new IdMapping();
                db.DeepCloneObjects(ids, db.CurrentSpaceId, mapping, false);
                mirrored = (BlockReference)tr.GetObject(mapping[source.ObjectId].Value, OpenMode.ForWrite);
            }
            mirrored.TransformBy(Matrix3d.Mirroring(axis));
            foreach (ObjectId id in mirrored.AttributeCollection)
            {
                var attRef = (AttributeReference)tr.GetObject(id, OpenMode.ForWrite);
                var pts = attRef.GetTextBoxCorners();
                attRef.TransformBy(Matrix3d.Rotation(Math.PI, pts[0].GetVectorTo(pts[3]), pts[0] + pts[0].GetVectorTo(pts[2]) / 2.0));
            }
        }

        struct ads_name
        {
            public IntPtr a;
            public IntPtr b;
        };

        [DllImport("acdb19.dll", CallingConvention = CallingConvention.Cdecl,
            EntryPoint = "?acdbGetAdsName@@YA?AW4ErrorStatus@Acad@@AAY01JVAcDbObjectId@@@Z")]
        static extern int acdbGetAdsName32(ref ads_name name, ObjectId objId);

        [DllImport("acdb19.dll", CallingConvention = CallingConvention.Cdecl,
            EntryPoint = "?acdbGetAdsName@@YA?AW4ErrorStatus@Acad@@AEAY01_JVAcDbObjectId@@@Z")]
        static extern int acdbGetAdsName64(ref ads_name name, ObjectId objId);

        static int acdbGetAdsName(ref ads_name name, ObjectId objId)
        {
            if (Marshal.SizeOf(IntPtr.Zero) > 4)
                return acdbGetAdsName64(ref name, objId);
            return acdbGetAdsName32(ref name, objId);
        }

        [DllImport("accore.dll", CharSet = CharSet.Unicode,
            CallingConvention = CallingConvention.Cdecl, EntryPoint = "acdbEntGet")]
        static extern System.IntPtr acdbEntGet(ref ads_name ename);

        [DllImport("accore.dll", CharSet = CharSet.Unicode,
            CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedTextBox")]
        static extern System.IntPtr acedTextBox(IntPtr rb, double[] point1, double[] point2);
    }

A testing command:

        [CommandMethod("TEST")]
        public static void Test()
        {
            var doc = AcAp.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            var peo = new PromptEntityOptions("\nSelect block: ");
            peo.SetRejectMessage("\nMust be a block.");
            peo.AddAllowedClass(typeof(BlockReference), true);
            var per = ed.GetEntity(peo);
            if (per.Status != PromptStatus.OK)
                return;
            var ppo = new PromptPointOptions("\nSpecify first point of mirror line: ");
            var ppr = ed.GetPoint(ppo);
            if (ppr.Status != PromptStatus.OK)
                return;
            var pt1 = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
            ppo.Message = "\nSpecify second point of mirror line: ";
            ppo.BasePoint = ppr.Value;
            ppo.UseBasePoint = true;
            ppr = ed.GetPoint(ppo);
            if (ppr.Status != PromptStatus.OK)
                return;
            var pt2 = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
            using (var tr = db.TransactionManager.StartTransaction())
            {
                var br = (BlockReference)tr.GetObject(per.ObjectId, OpenMode.ForRead);
                br.Mirror(new Line3d(pt1, pt2), false);
                tr.Commit();
            }
        }

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 6 of 10

dennis
Advisor
Advisor

Oddly I guess, Norman, I wasn't getting an error.  But i will go back and correct that.

0 Likes
Message 7 of 10

dennis
Advisor
Advisor

Gilles, thanks for the code, I do learn a lot reading through everything you write up.  However, I don't like the additional "make it do it" to have it work.  The problem I see would be downstream, should the user want to mirror it again, then they would have the code running to rotate the attributes within the block to make it right reading.  I have the Editor.Command("MIRROR" working, but I would have preferred to go with API, seems it is a bit "lacking".

0 Likes
Message 8 of 10

ActivistInvestor
Mentor
Mentor

Text and attributes have a dependence on the database's code page in use, and is also the reason why text and attribute alignment requires the current working database to be set. If the attribute or text is not database resident and is not using the default justification it requires special handling. Additionally, attribute or text that is mirrored about a vertical axis should have its alignment or justification changed as a result.

 

 

 

 

 


@dennis wrote:

Gilles, thanks for the code, I do learn a lot reading through everything you write up.  However, I don't like the additional "make it do it" to have it work.  The problem I see would be downstream, should the user want to mirror it again, then they would have the code running to rotate the attributes within the block to make it right reading.  I have the Editor.Command("MIRROR" working, but I would have preferred to go with API, seems it is a bit "lacking".


 

0 Likes
Message 9 of 10

_gile
Consultant
Consultant

I think I find a way to avoid most of the version dependant P/Invoke in the extension methods by building the ResultBuffer from the text properties.

 

    // inspired by: https://adndevblog.typepad.com/autocad/2013/10/mirroring-a-dbtext-entity.html
    public static class Extension
    {
        /// <summary>
        /// Gets the points of the text bounding box.
        /// </summary>
        /// <param name="dbText">Instance of DBText the method applies to.</param>
        /// <returns>The bounding box points in counterclockwise sense.</returns>
        public static Point3d[] GetTextBoxCorners(this DBText dbText)
        {
            if (dbText == null)
                throw new ArgumentNullException("dbText");
            int mirrored = dbText.IsMirroredInX ? 2 : 0;
            mirrored |= dbText.IsMirroredInY ? 4 : 0;
            var rb = new ResultBuffer(
                    new TypedValue(1, dbText.TextString),
                    new TypedValue(40, dbText.Height),
                    new TypedValue(41, dbText.WidthFactor),
                    new TypedValue(51, dbText.Oblique),
                    new TypedValue(7, dbText.TextStyleName),
                    new TypedValue(71, mirrored),
                    new TypedValue(72, (int)dbText.HorizontalMode),
                    new TypedValue(73, (int)dbText.VerticalMode));

            var point1 = new double[3];
            var point2 = new double[3];

            acedTextBox(rb.UnmanagedObject, point1, point2);

            var xform =
                Matrix3d.Displacement(dbText.Position.GetAsVector()) *
                Matrix3d.Rotation(dbText.Rotation, dbText.Normal, Point3d.Origin) *
                Matrix3d.WorldToPlane(new Plane(Point3d.Origin, dbText.Normal));

            return new[]
            {
                new Point3d(point1).TransformBy(xform),
                new Point3d(point2[0], point1[1], 0.0).TransformBy(xform),
                new Point3d(point2).TransformBy(xform),
                new Point3d(point1[0], point2[1], 0.0).TransformBy(xform)
            };
        }

        /// <summary>
        /// Mirrors the block reference.
        /// </summary>
        /// <param name="source">Instance of BlockReference the method applies to.</param>
        /// <param name="axis">Mirror axis line.</param>
        /// <param name="eraseSource">Value indicating if the source object have to be erased</param>
        public static void Mirror(this BlockReference source, Line3d axis, bool eraseSource)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (axis == null)
                throw new ArgumentNullException("axis");

            var db = source.Database;
            var tr = db.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            BlockReference mirrored;
            if (eraseSource)
            {
                mirrored = source;
                if (!mirrored.IsWriteEnabled)
                {
                    tr.GetObject(mirrored.ObjectId, OpenMode.ForWrite);
                }
            }
            else
            {
                var ids = new ObjectIdCollection(new[] { source.ObjectId });
                var mapping = new IdMapping();
                db.DeepCloneObjects(ids, db.CurrentSpaceId, mapping, false);
                mirrored = (BlockReference)tr.GetObject(mapping[source.ObjectId].Value, OpenMode.ForWrite);
            }
            mirrored.TransformBy(Matrix3d.Mirroring(axis));
            foreach (ObjectId id in mirrored.AttributeCollection)
            {
                var attRef = (AttributeReference)tr.GetObject(id, OpenMode.ForWrite);
                var pts = attRef.GetTextBoxCorners();
                attRef.TransformBy(Matrix3d.Rotation(Math.PI, pts[0].GetVectorTo(pts[3]), pts[0] + pts[0].GetVectorTo(pts[2]) / 2.0));
            }
        }

        /// <summary>
        /// Mirrors the DBText.
        /// </summary>
        /// <param name="source">Instance of BlockReference the method applies to.</param>
        /// <param name="axis">Mirror axis line.</param>
        /// <param name="eraseSource">Value indicating if the source object have to be erased.</param>
        public static void Mirror(this DBText source, Line3d axis, bool eraseSource)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (axis == null)
                throw new ArgumentNullException("axis");

            var db = source.Database;
            var tr = db.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            DBText mirrored;
            if (eraseSource)
            {
                mirrored = source;
                if (!mirrored.IsWriteEnabled)
                {
                    tr.GetObject(mirrored.ObjectId, OpenMode.ForWrite);
                }
            }
            else
            {
                var ids = new ObjectIdCollection(new[] { source.ObjectId });
                var mapping = new IdMapping();
                db.DeepCloneObjects(ids, db.CurrentSpaceId, mapping, false);
                mirrored = (DBText)tr.GetObject(mapping[source.ObjectId].Value, OpenMode.ForWrite);
            }
            mirrored.TransformBy(Matrix3d.Mirroring(axis));
            var pts = mirrored.GetTextBoxCorners();
            mirrored.TransformBy(Matrix3d.Rotation(Math.PI, pts[0].GetVectorTo(pts[3]), pts[0] + pts[0].GetVectorTo(pts[2]) / 2.0));
        }

        [DllImport("accore.dll", CharSet = CharSet.Unicode,
            CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedTextBox")]
        static extern System.IntPtr acedTextBox(IntPtr rb, double[] point1, double[] point2);
    }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 10 of 10

_gile
Consultant
Consultant

This one seems to give the same results as the native command.

 

    // inspired by: https://adndevblog.typepad.com/autocad/2013/10/mirroring-a-dbtext-entity.html
    public static class Extension
    {
        [DllImport("accore.dll", CharSet = CharSet.Unicode,
            CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedTextBox")]
        static extern System.IntPtr acedTextBox(IntPtr rb, double[] point1, double[] point2);

        /// <summary>
        /// Gets the extents of a DBText (mimics the 'textbox' LISP function)
        /// </summary>
        /// <param name="rb">DXF data decribing the DBText</param>
        /// <returns>The diagonal of the non rotated text with insertion point at (0, 0, 0).</returns>
        public static Extents3d TextBox(ResultBuffer rb)
        {
            var point1 = new double[3];
            var point2 = new double[3];
            acedTextBox(rb.UnmanagedObject, point1, point2);
            return new Extents3d(new Point3d(point1), new Point3d(point2));
        }

        /// <summary>
        /// Gets the WCS points of the text bounding box.
        /// </summary>
        /// <param name="dbText">Instance of DBText the method applies to.</param>
        /// <returns>The bounding box points in counterclockwise sense.</returns>
        public static Point3d[] GetTextBoxCorners(this DBText dbText)
        {
            if (dbText == null)
                throw new ArgumentNullException("dbText");

            int mirrored = dbText.IsMirroredInX ? 2 : 0;
            mirrored |= dbText.IsMirroredInY ? 4 : 0;
            var rb = new ResultBuffer(
                    new TypedValue(1, dbText.TextString),
                    new TypedValue(40, dbText.Height),
                    new TypedValue(41, dbText.WidthFactor),
                    new TypedValue(51, dbText.Oblique),
                    new TypedValue(7, dbText.TextStyleName),
                    new TypedValue(71, mirrored),
                    new TypedValue(72, (int)dbText.HorizontalMode),
                    new TypedValue(73, (int)dbText.VerticalMode));

            var point1 = new double[3];
            var point2 = new double[3];

            acedTextBox(rb.UnmanagedObject, point1, point2);

            var xform =
                Matrix3d.Displacement(dbText.Position.GetAsVector()) *
                Matrix3d.Rotation(dbText.Rotation, dbText.Normal, Point3d.Origin) *
                Matrix3d.WorldToPlane(new Plane(Point3d.Origin, dbText.Normal));

            return new[]
            {
                new Point3d(point1).TransformBy(xform),
                new Point3d(point2[0], point1[1], 0.0).TransformBy(xform),
                new Point3d(point2).TransformBy(xform),
                new Point3d(point1[0], point2[1], 0.0).TransformBy(xform)
            };
        }

        /// <summary>
        /// Mirrors the block reference retaining the attributes direction.
        /// </summary>
        /// <param name="source">Instance of BlockReference the method applies to.</param>
        /// <param name="axis">Mirror symmetry line.</param>
        /// <param name="eraseSource">Value indicating if the source object have to be erased</param>
        public static void Mirror(this BlockReference source, Line3d axis, bool eraseSource)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (axis == null)
                throw new ArgumentNullException("axis");

            var db = source.Database;
            var tr = db.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            BlockReference mirrored;
            if (eraseSource)
            {
                mirrored = source;
                if (!mirrored.IsWriteEnabled)
                {
                    tr.GetObject(mirrored.ObjectId, OpenMode.ForWrite);
                }
            }
            else
            {
                var ids = new ObjectIdCollection(new[] { source.ObjectId });
                var mapping = new IdMapping();
                db.DeepCloneObjects(ids, db.CurrentSpaceId, mapping, false);
                mirrored = (BlockReference)tr.GetObject(mapping[source.ObjectId].Value, OpenMode.ForWrite);
            }
            mirrored.TransformBy(Matrix3d.Mirroring(axis));
            foreach (ObjectId id in mirrored.AttributeCollection)
            {
                var attRef = (AttributeReference)tr.GetObject(id, OpenMode.ForWrite);
                var pts = attRef.GetTextBoxCorners();
                var cen = new LineSegment3d(pts[0], pts[2]).MidPoint;
                var rotAxis = Math.Abs(axis.Direction.Y) > Math.Abs(axis.Direction.X) ?
                    pts[0].GetVectorTo(pts[3]) :
                    pts[0].GetVectorTo(pts[1]);
                attRef.TransformBy(Matrix3d.Rotation(Math.PI, rotAxis, cen));
            }
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub