Monday, June 25, 2018

Change Width of DBText/AttributeReference by Its WidthFactor - Part Two: Drag-Fitting

In previous post, I showed the code to automatically adjust DBText's width to fit into a given space by reducing DBText's WidthFactor. Then I thought why not give user another handy way to adjust DBText's width: simply drag the DBText entity to desired width, much similar to AutoCAD's built-in dragging to scale an entity (but in this case, only width of DBText entity is "scaled").

Now that I am talking about dragging entities, I could derive my process from Jig class, or just build my own "Jig" style process with Transient graphics. I decided to go with the latter.

Here is the code:

using System;
 
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.GraphicsInterface;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace FitAttributeWidth
{
    public class TextWidthDragger
    {
        private Document _dwg;
        private Editor _ed;
        private ObjectId _txtId = ObjectId.Null;
        private double _factor = 1.0;
        private double _width = 0.0;
        private DBText _textDrawable = null;
        private Point3d _basePoint;
 
        private TransientManager _tsManager = TransientManager.CurrentTransientManager;
 
 
        public TextWidthDragger()
        {
            
        }
 
        public void DragTextEntity()
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
            _ed = _dwg.Editor;
 
            var entId = SelectDragTarget();
            if (entId.IsNull)
            {
                _ed.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            DragDBText(_dwg, entId);
        }
 
        public void DragDBText(Document dwg, ObjectId txtId)
        {
            _dwg = dwg;
            _ed = dwg.Editor;
            _txtId = txtId;
 
            DoTextDrag();
        }
 
        #region private methods: select DBText or AttributeReference
 
        private ObjectId SelectDragTarget()
        {
            var entId = ObjectId.Null;
 
            var opt = new PromptKeywordOptions(
                "\nSelect a Text or an Attribute in block:");
            opt.AppendKeywordsToMessage = true;
            opt.Keywords.Add("Text");
            opt.Keywords.Add("Attribute");
            opt.Keywords.Default = "Text";
 
            var res = _ed.GetKeywords(opt);
            if (res.Status== PromptStatus.OK)
            {
                if (res.StringResult == "Text")
                    entId = SelectDBText();
                else
                    entId = SelectAttribute();
            }
 
            return entId;
        }
 
        private ObjectId SelectDBText()
        {
            var txtId = ObjectId.Null;
 
            var opt = new PromptEntityOptions(
                "\nSelect a Text entity:");
            opt.SetRejectMessage("\nInvalid selection: not a text entity!");
            opt.AddAllowedClass(typeof(DBText), true);
 
            var res = _ed.GetEntity(opt);
            if (res.Status== PromptStatus.OK)
            {
                txtId = res.ObjectId;
            }
 
            return txtId;
        }
 
        private ObjectId SelectAttribute()
        {
            var attId = ObjectId.Null;
 
            while (true)
            {
                var opt = new PromptNestedEntityOptions(
                    "\nSelect an Attribute in block:");
                opt.AllowNone = false;
 
                var res = _ed.GetNestedEntity(opt);
                if (res.Status == PromptStatus.OK)
                {
                    if (res.ObjectId.ObjectClass.DxfName.ToUpper() == "ATTRIB")
                    {
                        attId = res.ObjectId;
                    }
                    else
                    {
                        _ed.WriteMessage(
                            "\nInvalid selection: not an attribue in block!");
                    }
                }
                else
                {
                    break;
                }
 
                if (!attId.IsNull) break;
            }
 
            return attId;
        }
 
        #endregion
 
        #region private methods
 
        private void DoTextDrag()
        {
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var target = (DBText)tran.GetObject(_txtId, OpenMode.ForRead);
                _factor = target.WidthFactor;
                _width = GetTextWidth(target) / _factor;
                _basePoint = target.Position;
 
                bool dragOk = false;
                try
                {
                    _textDrawable = (DBText)target.Clone();
                    _textDrawable.ColorIndex = 2;
 
                    _ed.PointMonitor += Editor_PointMonitor;
 
                    var dist = GetDistance();
                    if (dist!=double.MinValue)
                    {
                        _factor = dist / _width;
                        target.UpgradeOpen();
                        target.WidthFactor = _factor;
                    }
                }
                finally
                {
                    ClearTransients();
                    _textDrawable.Dispose();
                    _ed.PointMonitor -= Editor_PointMonitor;
                }
 
                if (dragOk && _factor!=target.WidthFactor)
                {
                    target.UpgradeOpen();
                    target.WidthFactor = _factor;
                }
 
                tran.Commit();
            }
        }
 
        private double GetTextWidth(DBText txt)
        {
            var ext = txt.GeometricExtents;
            return Math.Abs(ext.MaxPoint.X - ext.MinPoint.X);
        }
 
        private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
        {
            ClearTransients();
 
            var dist = e.Context.RawPoint.DistanceTo(_basePoint);
            _factor = dist / _width;
 
            e.AppendToolTipText($"Width scale factor: {_factor}");
 
            _textDrawable.WidthFactor = _factor;
 
            AddTransients();
        }
 
        private void AddTransients()
        {
            _tsManager.AddTransient(
                _textDrawable, 
                TransientDrawingMode.Highlight, 
                128, 
                new IntegerCollection());
        }
 
        private void ClearTransients()
        {
            _tsManager.EraseTransient(_textDrawable, new IntegerCollection());
        }
 
        private double GetDistance()
        {
            var opt = new PromptDistanceOptions(
                "\nSet text entity width scale factor:");
            opt.AllowZero = false;
            opt.AllowNegative = false;
            opt.AllowArbitraryInput = false;
            opt.AllowNone = false;
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.UseDashedLine = true;
 
            var res = _ed.GetDistance(opt);
            if (res.Status == PromptStatus.OK)
                return res.Value;
            else
                return double.MinValue;
        }
 
        #endregion
    }
}

Here is the command to run it:

[CommandMethod("DragTextWidth")]
public static void DragTextWidth()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    try
    {
        var dragger = new TextWidthDragger();
        dragger.DragTextEntity();
    }
    catch (System.Exception ex)
    {
        ed.WriteMessage("\nError:\n{0}.", ex.Message);
    }
    finally
    {
        Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
    }
}

Again, as mentioned in previous post, for simplicity, when calculate entity's width I assume the DBText/AttributeReference entity is placed horizontally. The code also tries to show the DBText's WidthFactor dynamically as tooltip when the DBText entity is dragged. However, since the tooltip thing is handled in PointMonitor event in the simplest way, the tooltip only shows when mouse cursor stays not moving. It might be possible to use the same or similar approach showed in this post to make a good tooltip to show changing WidthFactor while dragging. Again, for the simplicity of this post, I decide not spend time on this for now.

See this video clip showing how the code works.


1 comment: