Changing text entity's overall width with its WidthFactor from Properties window is an easy and straightforward "try-and-see" process. However, in the case of attribute in a block (AttributeReference, which is derived from DBText, hence its overall widht can also be changed with its WidthFactor), it would be not that easy/straightforward to set its WidthFactor via Properties window.
Furthermore, as AutoCAD programmers, we often programmatically set text string of DBText or AttributeReference entities. In this case, we usually do not have the freedom to change the words/characters of the text string to reduce the overall width of the entities. Therefore, it would be natural to use code to automatically set WidthFactor to a proper value so that that the width of an DBText entity would fit a given space. For example, many drawing title blocks/borders have fields or tables to be filled with either DBTexts, or AttributeReferences programmatically. It would be good the application is able to adjust the width of DBText/AttributeReference to fit the text string in given space (width of the field or table column).
Assume I am in this situation: I need to use code to fill out a table with data from project database as text, or block's attribute. The text values could be too long to fit into the table's column width from time to time. So, as long as I know the width of the columns/fields, I want to shrink the text/attribute entity's width by change its WidthFactor automatically, so that user does not have to examine the table being updated by my application visually for the unfit texts.
The code to do the "auto-fit" work is really simple, shown below, as an extension method to ObjectId:
public static class TextWidthExtension { public static bool FitTextWidth( this ObjectId entId, double width, double minWidthFactor=0.5) { var dfxName = entId.ObjectClass.DxfName.ToUpper(); if (dfxName!="ATTRIB" && dfxName!="TEXT") { throw new ArgumentException( "Entity must be either DBText, or AttributeReference!"); } bool fit = false; using (var tran = entId.Database.TransactionManager.StartTransaction()) { var txtEnt = (DBText)tran.GetObject(entId, OpenMode.ForWrite); fit = FitToWidth(txtEnt, width, minWidthFactor); if (fit) tran.Commit(); else tran.Abort(); } return fit; } private static bool FitToWidth(DBText txt, double maxWidth, double minWidFactor) { var txtW = GetTextWidth(txt); bool fit = true; double factor = txt.WidthFactor; while (txtW > maxWidth && factor >= minWidFactor) { fit = false; factor -= 0.05; txt.WidthFactor = factor; txtW = GetTextWidth(txt); } fit = txtW < maxWidth; return fit; } private static double GetTextWidth(DBText txt) { var ext = txt.GeometricExtents; return Math.Abs(ext.MaxPoint.X - ext.MinPoint.X); } }
This method takes an optional input to indicate the minimum value of WidthFactor of an DBText. In reality, it would make little sense to set WidthFactor value too small. Also, in the shown code, for simplicity, I calculate the width of a DBText entity with its GeometricExtents, assuming the DBText entity is horizontal (its Rotation=0.0). I could have also made the decrement of the WdithFactor in the "while{...}" loop a input parameter of the method instead of hard-coded as "0.05".
Here is the command to run the code:
using System.Collections.Generic; using System.Linq; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(FitAttributeWidth.MyCommands))] namespace FitAttributeWidth { public class MyCommands { [CommandMethod("FitAtts")] public static void FitAttributeToSpace() { var dwg = CadApp.DocumentManager.MdiActiveDocument; var ed = dwg.Editor; try { var attIds = GetBlockAttributeIds(ed); if (attIds.Count()>0) { foreach (var id in attIds) { id.FitTextWidth(60.0, 0.7); } } } catch (System.Exception ex) { ed.WriteMessage("\nError:\n{0}.", ex.Message); } finally { Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt(); } } [CommandMethod("FitText")] public static void FitTextToSpace() { var dwg = CadApp.DocumentManager.MdiActiveDocument; var ed = dwg.Editor; try { while(true) { var txtId = SelectText(ed); if (!txtId.IsNull) { txtId.FitTextWidth(60.0, 0.7); } else { break; } } } catch (System.Exception ex) { ed.WriteMessage("\nError:\n{0}.", ex.Message); } finally { Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt(); } } #region private methods private static IEnumerable<ObjectId> GetBlockAttributeIds(Editor ed) { var lst = new List<ObjectId>(); var opt = new PromptEntityOptions( "\nSelect the block for fitting its attributes:"); opt.SetRejectMessage("\nInvalid selection: not a block."); opt.AddAllowedClass(typeof(BlockReference), true); var res = ed.GetEntity(opt); if (res.Status==PromptStatus.OK) { using (var tran = res.ObjectId.Database.TransactionManager.StartTransaction()) { var bref = (BlockReference)tran.GetObject(res.ObjectId, OpenMode.ForRead); if (bref.AttributeCollection.Count>0) { lst.AddRange(bref.AttributeCollection.Cast<ObjectId>()); } tran.Commit(); } } return lst; } private static ObjectId SelectText(Editor ed) { ObjectId id = ObjectId.Null; var opt = new PromptEntityOptions( "\nSelect a Text entity in the table:"); opt.SetRejectMessage("\nInvalid: not a DBText!"); opt.AddAllowedClass(typeof(DBText), true); var res = ed.GetEntity(opt); if (res.Status== PromptStatus.OK) { id = res.ObjectId; } return id; } #endregion } }
This video clip shows the result of code execution.
INTERESTING!!
ReplyDeleteGOOD CONTENT
CAD to BIM conversion in Uk
Thanks for info
ReplyDeleteautocad drafting in UK
Yes, the code in this article should work with all versions of AutoCAD since AutoCAD 2013. Sorry, I did not keep the source code project somewhere. You have to create an AutoCAD .NET plugin project and copy the code from here. Hopefully, you do know how to build AutoCAD .NET plugin project with Visual Studio.
ReplyDelete