Saturday, December 31, 2016

Splitting Polyline/Curve With AutoCAD Civil 3D Labels Associated - Updated

The code shown in my previous post, as I mentioned there, only re-creates single Civil3D segment label when a polyline/curve is split. However, each segment of a split-able curve/polyline could be annotated by multiple labels (in different label styles, of course). Also, the code shown in that post does not re-store the labels in their original locations. I was able to find a few hours sitting down to update the code to cover these issues.

The solution is to search drawing to find all labels that associate to the polyline/curve to be split, and save these labels' information for later need (when re-creating labels). So, I defined this class SegmentLabelInfo:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
 
namespace Civil3DLabels
{
    public enum SegmentLabelType
    {
        LineLabel=0,
        CurveLabel=1,
    }
 
    public class SegmentLabelInfo
    {
        public ObjectId LabelStyleId { setget; }
        public string LabelLayer { setget; }
        public Point3d LabelPosition { setget; }
        public SegmentLabelType LabelType { setget; }
 
        public SegmentLabelInfo()
        {
            LabelStyleId = ObjectId.Null;
            LabelLayer = "";
            LabelPosition = Point3d.Origin;
            LabelType = SegmentLabelType.LineLabel;
        }
 
        public bool IsOnCurve(ObjectId curveId, out double ratio)
        {
            ratio = 0.5;
            bool onCurve = false;
 
            Point3d pt;
            using (var tran = 
                curveId.Database.TransactionManager.StartTransaction())
            {
                var curve = (Curve)tran.GetObject(
                    curveId, OpenMode.ForRead);
                pt = curve.GetClosestPointTo(LabelPosition, false);
 
                //If the label anchored on this curve
                var dist = LabelPosition.DistanceTo(pt);
                onCurve = dist <= Tolerance.Global.EqualPoint;
 
                //the distance from curve's start point to anchor point
                dist = curve.GetDistAtPoint(pt);
                ratio = dist / curve.GetDistAtPoint(curve.EndPoint);
 
                tran.Commit();
            }
 
            return onCurve;
        }
    }
}

As the code shows, besides the 4 public properties used to store a label's information, a method IsOnCurve() is also defined to determine if the label sits on one of the segment split from the original polyline/curve; if yes, what location (distance to the start point) the label is at (output parameter ratio).

Now, here is the updated working code, where I changed the class name from PolylineSplitter to MyPolylineSplitter:

using System.Collections.Generic;
using System.Linq;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using CivilApp = Autodesk.Civil.ApplicationServices.CivilApplication;
using Civil = Autodesk.Civil;
using CivilDb = Autodesk.Civil.DatabaseServices;
using Autodesk.Civil.DatabaseServices.Styles;
 
[assemblyCommandClass(typeof(Civil3DLabels.MyPolylineSplitter))]
 
namespace Civil3DLabels
{
    public class MyPolylineSplitter
    {
        private const string DXF_LABEL = "AECC_GENERAL_SEGMENT_LABEL";
 
        [CommandMethod("ExplodePoly")]
        public void DoSplit()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
            try
            {
                Split(dwg);
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError: {0}", ex.Message);
                ed.WriteMessage("\n*Cancel*");
            }
 
            Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
        }
        public void Split(Document dwg)
        {
            CadDb.ObjectId polyId = SelectPolyline(dwg.Editor);
            if (polyId.IsNull) return;
 
            //Collect all labels' information that associate with the polyline
            IEnumerable<SegmentLabelInfo> labels = FindAssociatedLabels(polyId);
            
            //Get default line/curve label style IDs
            var defaultLineLabelStyleId = CadDb.ObjectId.Null;
            var defaultCurveLabelStyleId = CadDb.ObjectId.Null;
            if (labels.Count() > 0)
            {
                GetSettingsCmdAddSegmentLabelDefaultStyles(
                    out defaultLineLabelStyleId, out defaultCurveLabelStyleId);
            }
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var poly = (CadDb.Polyline)tran.GetObject(
                    polyId, CadDb.OpenMode.ForRead);
 
                //Find current space
                var curSpace = (CadDb.BlockTableRecord)tran.GetObject(
                    poly.OwnerId, CadDb.OpenMode.ForRead);
 
                //Newly created entities because of the splitting
                var newIds = new List<CadDb.ObjectId>();
 
                //Get split segments and add them into current space
                using (var ents = GetSplitCurvesFromPolyline(poly))
                {
                    if (ents.Count > 1)
                    {   
                        curSpace.UpgradeOpen();
                        foreach (CadDb.DBObject ent in ents)
                        {
                            var id = curSpace.AppendEntity(ent as CadDb.Entity);
                            tran.AddNewlyCreatedDBObject(ent, true);
 
                            newIds.Add(id);
                        }
 
                        // Erase the polyline,
                        // Associated labels would also be gone
                        poly.UpgradeOpen();
                        poly.Erase(true);
                    }
                    else
                    {
                        ents[0].Dispose();
                    }
                }
 
                //Add label to each newly added entities
                if (newIds.Count>0 && labels.Count()>0)
                {      
                    foreach (var entId in newIds)
                    {
                        AddSegmentLabel(
                            entId, 
                            labels, 
                            defaultLineLabelStyleId, 
                            defaultCurveLabelStyleId,
                            tran);
                    }
                }
 
                tran.Commit();
            }         
        }
 
        #region private methods
 
        private CadDb.ObjectId SelectPolyline(Editor ed)
        {
            var opt = new PromptEntityOptions(
                "\nSelect a polyline:");
            opt.SetRejectMessage("\nInvalid: not a polyline.");
            opt.AddAllowedClass(typeof(CadDb.Polyline), true);
 
            var res = ed.GetEntity(opt);
            if (res.Status==PromptStatus.OK)
            {
                return res.ObjectId;
            }
            else
            {
                return CadDb.ObjectId.Null;
            }
        }
 
        private CadDb.DBObjectCollection GetSplitCurvesFromPolyline(
            CadDb.Polyline poly)
        {
            var points = new Point3dCollection();
            for (int i = 0; i < poly.NumberOfVertices; i++)
            {
                points.Add(poly.GetPoint3dAt(i));
            }
 
            var dbObjects = poly.GetSplitCurves(points);
 
            return dbObjects;
        }
 
        private IEnumerable<SegmentLabelInfo> FindAssociatedLabels(
            CadDb.ObjectId polyId)
        {
            var labels = new List<SegmentLabelInfo>();
 
            CadDb.Database db = polyId.Database;
 
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var ent = (CadDb.Entity)tran.GetObject(
                    polyId, CadDb.OpenMode.ForRead);
                var space = (CadDb.BlockTableRecord)tran.GetObject(
                    ent.OwnerId, CadDb.OpenMode.ForRead);
 
                foreach (CadDb.ObjectId id in space)
                {
                    if (id.ObjectClass.DxfName.ToUpper() == DXF_LABEL)
                    {
                        var label = tran.GetObject(
                            id, CadDb.OpenMode.ForRead) as CivilDb.Label;
                        if (label != null && label.FeatureId==polyId)
                        {
                            var info = new SegmentLabelInfo()
                            {
                                LabelStyleId = label.StyleId,
                                LabelLayer = label.Layer,
                                LabelPosition = label.AnchorInfo.Location
                            };
 
                            // Since each label uses either line label style or curve label style
                            // We test if the label style is line label style.
                            // if not, the label style must be curve label style
                            var lineStyles =
                                CivilApp.ActiveDocument.Styles.LabelStyles.GeneralLineLabelStyles;
                            info.LabelType = IsSegmentLabelStyle(label.StyleId, lineStyles, tran) ?
                                SegmentLabelType.LineLabel : SegmentLabelType.CurveLabel;
 
                            labels.Add(info);
                        }
                    }
                }
 
                tran.Commit();
            }
 
            return labels;
        }
 
        private void AddSegmentLabel(
            CadDb.ObjectId entId, 
            IEnumerable<SegmentLabelInfo> labels,
            CadDb.ObjectId defaultLineLabelStyleId, 
            CadDb.ObjectId defaultCurveLabelStyleId, 
            CadDb.Transaction tran )
        {
            foreach (var labelInfo in labels)
            {
                double ratio;
                if (labelInfo.IsOnCurve(entId, out ratio))
                {
                    var lineLabel = labelInfo.LabelType == SegmentLabelType.LineLabel ?
                        labelInfo.LabelStyleId : defaultLineLabelStyleId;
                    var curveLabel = labelInfo.LabelType == SegmentLabelType.CurveLabel ?
                        labelInfo.LabelStyleId : defaultCurveLabelStyleId;
 
                    //Add label to the segment
                    var id = CivilDb.GeneralSegmentLabel.Create(
                        entId, ratio, lineLabel, curveLabel);
                    var label = (CivilDb.Label)tran.GetObject(
                        id, CadDb.OpenMode.ForRead);
                    if (label.Layer.ToUpper()!=labelInfo.LabelLayer.ToUpper())
                    {
                        label.UpgradeOpen();
                        label.Layer = labelInfo.LabelLayer;
                    }
                }
            }
        }
 
        private bool IsSegmentLabelStyle(
            CadDb.ObjectId styleId, 
            IEnumerable<CadDb.ObjectId> styleCollection, 
            CadDb.Transaction tran)
        {
            bool found = false;
 
            foreach (CadDb.ObjectId id in styleCollection)
            {
                if (styleId == id)
                {
                    found = true;
                }
                else
                {
                    var style = (LabelStyle)tran.GetObject(
                        id, CadDb.OpenMode.ForRead);
                    if (style.ChildrenCount > 0)
                    {
                        var styleIds = new List<CadDb.ObjectId>();
                        for (int i = 0; i < style.ChildrenCount; i++)
                        {
                            styleIds.Add(style[i]);
                        }
 
                        //Recursive call
                        found =
                            IsSegmentLabelStyle(styleId, styleIds, tran);
                    }
                }
 
                if (found) break;
            }
 
            return found;
        }
 
        private void GetSettingsCmdAddSegmentLabelDefaultStyles(
            out CadDb.ObjectId lineLabelStyleId,
            out CadDb.ObjectId curveLabelStyleId)
        {
            var settings = CivilApp.ActiveDocument.Settings;
            var cmdSettings = 
                settings.GetSettings<Civil.Settings.SettingsCmdAddSegmentLabel>();
 
            lineLabelStyleId = cmdSettings.Styles.LineLabelStyleId.Value;
            curveLabelStyleId = cmdSettings.Styles.CurveLabelStyleId.Value;
        }
        
        #endregion
    }
}

The polyline to be split in this video clip is annotated with 2 labels for each of its segment; some of the labels are on different layers (in different colors); Also, the labels' anchor point (location) on the segment have been dragged randomly along the segment. After execute command "ExplodePoly", all labels look like unchanged, even though they are all re-created.

1 comment: