Showing posts with label ObjectARX .NET API. Show all posts
Showing posts with label ObjectARX .NET API. Show all posts

Monday, October 14, 2019

Proceed Or Cancel Entity Erasing By Asking For Confirmation

A recent discussion in Autodesk's .NET user forum raises the question of how to ask user confirmation when entity is to be erased.

AutoCAD has very good "UNDO" mechanism built in from beginning. In majority of cases when entities to be erased from drawing, asking user to confirm the erasing not only unnecessary, but also quite annoying and interrupting. However, there may be cases when asking for confirmation is legitimate and/or desired. For example, if you have some entities associated to many other entities or data (XData, ExtensionDictionary...) via your application, when these entities are to be erased, you might want to do something, or let user know the consequence of losing some data.

The solution of the original poster of the discussion was to ask user's for confirmation after the fact of erasing, and only bring the erased entities back with "UNDO", if the user does not want the erasing. However, I though it is possible to ask for confirmation before the erasing and only proceed with user's confirmation. Of course, asking confirmation should only target specific entities (the less the better). This led me to turn to Overrule, namely ObjectOverrule.

Overrule was made available via API (AutoCAD 2010, I think). Kean Walmsley (unfortunately he does no longer write about AutoCAD programming) posted an article in his famous block Through the Interface on how to prevent AutoCAD objects from being erased. From his article we can see, it is really easy to stop erasing. It should also be easier to allow some conditions being applied so that the overrule can decide whether erasing goes ahead of not. Also, we can take advantage of Overrule's filtering mechanism to narrow down the scope of target entities easily.

So, I decided give ObjectOverrule a try to see what kind of solution and its usability it would lead to. Here is the custom ObjectOverrule class EraseOverrule:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace OverruledEntityErasing
{
    public enum EraseOverruleMode
    {
        RaiseEvent = 0,
        GatherIds = 1,
        VetoErase = 2,
    }
 
    public class EraseOverrule : ObjectOverrule
    {
        private static EraseOverrule _instance = null;
        private bool _originalOverruling = Overrule.Overruling;
        private IEnumerable<string> _layers = null;
 
        public EraseOverruleMode Mode { setget; } = EraseOverruleMode.RaiseEvent;
        public List<ObjectId> EntitiesToErase { get; } = new List<ObjectId>();
 
        public static EraseOverrule Instance
        {
            get
            {
                if (_instance==null)
                {
                    _instance = new EraseOverrule();
                }
                return _instance;
            }
        }
 
        public event AttemptEraseEventHandler AttemptErase;
        public void Start(IEnumerable<stringlayerFilters)
        {
            _layers = layerFilters;
            EntitiesToErase.Clear();
 
            Overrule.AddOverrule(RXClass.GetClass(typeof(Entity)), thisfalse);
            this.SetCustomFilter();
            Overrule.Overruling = true;
        }
 
        public void Stop()
        {
            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Entity)), this);
            Overrule.Overruling = _originalOverruling;
        }
 
        public override bool IsApplicable(RXObject overruledSubject)
        {
            if (_layers == null || _layers.Count() == 0) return true;
 
            var filtered = FilteredByLayers(overruledSubject as Entity);
            return filtered;
        }
 
        public override void Erase(DBObject dbObjectbool erasing)
        {
            base.Erase(dbObjecterasing);
 
            // If user does "UNDO" to unerase entity back
            // bypass custom overrule process bellow.
            if (!erasingreturn; 
 
            switch (Mode)
            {
                case EraseOverruleMode.VetoErase:
                    throw new Autodesk.AutoCAD.Runtime.Exception(
                        ErrorStatus.NotApplicable);
 
                case EraseOverruleMode.GatherIds:
                    EntitiesToErase.Add(dbObject.ObjectId);
                    throw new Autodesk.AutoCAD.Runtime.Exception(
                        ErrorStatus.NotApplicable);
 
                case EraseOverruleMode.RaiseEvent:
                    var cancel = false;
                    var args = new AttemptEraseEventArgs(dbObject.ObjectId);
                    AttemptErase?.Invoke(thisargs);
                    cancel = args.CancelErasing;
                    if (cancel)
                    {
                        throw new Autodesk.AutoCAD.Runtime.Exception(
                            ErrorStatus.NotApplicable);
                    }
                    break;
            }  
        }
 
        private bool FilteredByLayers(Entity ent)
        {
            var layer = ent.Layer.ToUpper();
            foreach (var l in _layers)
            {
                if (l.ToUpper() == layerreturn true;
            }
            return false;
        }
    }
 
    public class AttemptEraseEventArgs : EventArgs
    {
        public ObjectId EntityId { private setget; }
        public bool CancelErasing { setget; } = false;
        public AttemptEraseEventArgs(ObjectId entId)
        {
            EntityId = entId;
        }
    }
 
    public delegate void AttemptEraseEventHandler(object senderAttemptEraseEventArgs e);
}

The EraseOverrule is derived from ObjectOverrule and overrides its Erase() method. Here are some considerations about this class:

1. I defined an enum type EraseOverruleMode. Each mode results in different behaviour of the overrule (i.e different code logic in overridden Erase() method;

  • EraseOverruleMode.RaiseEvent: an AttemptEraseEventHandler is uased as event, which is raised in overridden Erase() method. This allows external code process, which enables the overrule and subscribes this event to decide if the erasing is to be cancelled or not.;
  • EraseOverruleMode.GatherIds: all erasing to the target entities is cancelled and the ObjectIds of these entities are collected in a collection. The external code process can then test if "erasing attempt" has been applied to some entities. If yes, the code can decide if go ahead with erasing or not. If yes, stop the overrule and do the normal erasing;
  • EraseOverruleMode.VetoErase: this simply veto erasing on target entities.
The code shown later demonstrates how these EraseOverruleModes are used in real AutoCAD erasing process, either when erasing is done with .NET API, or done with AutoCAD built-in commands.

2. Considering EraseOverrule is used either to overrule erasing in .NET API code, or overrule AutoCAD built-in erasing (by "ERASE" command, or other commands that may result in entity being erased), I made it can only be instantiated as singleton object.

3. During debugging, I realized that the Erase() method in the overrule is called both when object is erased and the object is "unerased" by UNDO command. When erased entity is undone, the Erase() method's "bool erasing" argument is false. So, I needed to have this line

if (!erasing) return;

before my overrule logic, which are only needed to handle erasing of target entities.

4. Raising exception in overrule's overridden Erase() method stop erasing works well when the the overrule is used to overrule erasing done by AutoCAD built-in commands, because AutoCAD handles this raised exception in someway. However, if you have .NET API code that does the erasing (i.e. opening an entity for write in a transaction, and call Entity.Erase() method), your code MUST enclose the Entity.Erase() call in a try{...}catch{} block with a blank catch... clause.; or your program would crash AutoCAD because of the exception raised in the overrule. Also, during debugging, the throw new Exception...statement ALWAYS break the debugging run, you can hit F5 to let the debugging run continue ONLY if your code that calls Entity.Erase() has try{...}catch{} wrapped (i.e. the exception is handled).

5. For simplicity, I use Layers as the filter for the EraseOverrule. That means when EraseOverrule is in effect, entities on certain Layers are protected from being erased without being confirmed. It is very easy to code EraseOverrule to filter target objects differently based on needs.

Now move on to see how to use EraseOverrule in 2 different situations: using it with custom erasing process built with AutoCAD .NET API (i.e. doing erasing with our own code in conjunction with this overrule); or using it with AutoCAD built-in erasing process (i.e. ding erasing with AutoCAD built-in commands while the overrule is in effect).

I defined class SpecialEraser that uses EraseOverrule to govern the coded erasing, so that target entities by EraseOverrule would only be erased with user's confirmation. Here is its code:

using Autodesk.AutoCAD.DatabaseServices;
using System.Collections.Generic;
using System.Linq;
 
namespace OverruledEntityErasing
{
    public class SpecialEraser
    {
        private List<ObjectId> _entitiesNotErased = new List<ObjectId>();
        private int _erasedCount  = 0;
        private int _notErasedCount = 0;
        private string[] _protectedLayers = null;
        public SpecialEraser(string[] protectedLayers)
        {
            _protectedLayers = protectedLayers;
        }
 
        #region public methods
 
        public void ConfirmedEraseOneByOne(ObjectId[] entIds)
        {
            if (entIds.Length == 0) return;
            _erasedCount = 0;
            _notErasedCount = 0;
 
            EraseOverrule.Instance.Mode = EraseOverruleMode.RaiseEvent;
            EraseOverrule.Instance.AttemptErase += AttemptIndividualEraseHandler;
            EraseOverrule.Instance.Start(_protectedLayers);
 
            // when each entity is to be erased, if the entity is the overrule's 
            // target, the event handler allow a chance for user to confirm, 
            // in which user can cancel the erasing
            try
            {
                DoErase(entIds);
            }
            finally
            {
                EraseOverrule.Instance.AttemptErase -= AttemptIndividualEraseHandler;
                EraseOverrule.Instance.Stop();
            }
 
            var normalErased = entIds.Length - (_erasedCount + _notErasedCount);
            var msg = 
                $"Normally erased count: {normalErased}\n" +
                $"Confirmedly erased count: {_erasedCount}\n" +
                $"Confirmedly not erased count: {_notErasedCount}";
            MsgBox.ShowInfo(msg);
        }
 
        public void ConfirmedEraseInBatch(ObjectId[] entIds)
        {
            if (entIds.Length == 0) return;
            _entitiesNotErased.Clear();
 
            EraseOverrule.Instance.Mode = EraseOverruleMode.RaiseEvent;
            EraseOverrule.Instance.AttemptErase += AttemptBatchEraseHandler;
            EraseOverrule.Instance.Start(_protectedLayers);
 
            // Do the erasing, if the entity is the overrule's target, the erasing
            // will be all denied, and the entity's objectId is collected for 
            // later to be confirned for real erasing  
            try
            {  
                DoErase(entIds);
            }
            finally
            {
                EraseOverrule.Instance.AttemptErase -= AttemptBatchEraseHandler;
                EraseOverrule.Instance.Stop();
            }
 
            // do actual erasing here at once
            if (_entitiesNotErased.Count>0)
            {
                var erased = entIds.Length - _entitiesNotErased.Count;
                var msg = 
                    $"Erased entity count: {erased}\n" +
                    $"Erase-protected entity count: {_entitiesNotErased.Count}" +
                    "\n\nDo you really want to erase protected entities?";
 
                if (MsgBox.ShowYesNo(msg)
                    == System.Windows.Forms.DialogResult.Yes)
                {
                    DoErase(_entitiesNotErased);
                }
            }
        }
 
        public void ProtectedErase(ObjectId[] entIds)
        {
            if (entIds.Length == 0) return;
 
            EraseOverrule.Instance.Mode = EraseOverruleMode.VetoErase;
            EraseOverrule.Instance.Start(_protectedLayers);
 
            // Do the erasing, if the entity is the overrule's target,
            // erasing is vetoed.
            try
            {
                DoErase(entIds);
            }
            finally
            {
                EraseOverrule.Instance.Stop();
            }
 
            int count = (from id in entIds where !id.IsErased select id).Count();
            var msg = 
                $"{count} out of {entIds.Length} " +
                $"entit{(entIds.Length > 1 ? "ies" : "y")} is proected, thus not erased.";
            MsgBox.ShowInfo(msg);
        }
 
        public List<ObjectIdProtectedEraseWithEntityIds(ObjectId[] entIds)
        {
            if (entIds.Length == 0) return new List<ObjectId>();
 
            List<ObjectIdids = null;
 
            EraseOverrule.Instance.Mode = EraseOverruleMode.GatherIds;
            EraseOverrule.Instance.Start(_protectedLayers);
 
            // Do the erasing, if the entity is the overrule's target,
            // erasing is vetoed, and entity's Id is collected
            try
            {
                DoErase(entIds);
                ids = EraseOverrule.Instance.EntitiesToErase;
            }
            finally
            {
                EraseOverrule.Instance.Stop();
            }
 
            return ids;
        }
 
        public static void DoErase(IEnumerable<ObjectIdentIds)
        {
            using (var tran = 
                entIds.First().Database.TransactionManager.StartTransaction())
            {
                foreach (var id in entIds)
                {
                    var ent = tran.GetObject(idOpenMode.ForWrite);
                    try
                    {
                        ent.Erase();
                    }
                    catch { }
                }
                tran.Commit();
            }
        }
 
        #endregion
 
        #region private methods
 
        private void AttemptIndividualEraseHandler(object senderAttemptEraseEventArgs e)
        {
            var entType = e.EntityId.ObjectClass.DxfName;
            var msg = $"Entity to be erased: {entType}\n\n" +
                "Do you want to erase it?";
 
            if (MsgBox.ShowYesNo(msg) == System.Windows.Forms.DialogResult.No)
            {
                e.CancelErasing = true;
                _notErasedCount++;
            }
            else
            {
                _erasedCount++;
            }
        }
 
        private void AttemptBatchEraseHandler(object senderAttemptEraseEventArgs e)
        {
            _entitiesNotErased.Add(e.EntityId);
            e.CancelErasing = true;
        }
 
        #endregion
    }
}


The code in SpecialEraser class is fairly self-explanatory. Here is the CommandClass that actually uses the SpecialEraser to do the erasing by asking user to confirm of erasing entities "protected by" EraseOverrule; or simply enables EraseOverrule against erasing done by AutoCAD's built-in commands, where CommandEnded event is handled to determine if there was an attempt made to erase entities "protected by" EraseOverrule, if yes, the real erasing only occurs after user's confirmation. Here is the CommandClass code:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(OverruledEntityErasing.MyCommands))]
 
namespace OverruledEntityErasing
{
    public class MyCommands
    {
        private static bool _overruleOn = false;
        private static string[] _protectedLayers = new string[] { "MyLayer1""MyLayer2" };
 
        #region Command methods: erasing entities with "SpecialEraser" class
 
        [CommandMethod("ConfirmedEraseInBatch"CommandFlags.UsePickSet)]
        public static void DoConfirmedEraseInBatch()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var entIds = SelectEntities(ed);
            if (entIds == nullreturn;
 
            _overruleOn = false;
            var eraser = new SpecialEraser(_protectedLayers);
            eraser.ConfirmedEraseInBatch(entIds);
        }
 
        [CommandMethod("ConfirmedEraseOneByOne"CommandFlags.UsePickSet)]
        public static void DoConfirmedEraseOneByOne()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var entIds = SelectEntities(ed);
            if (entIds == nullreturn;
 
            _overruleOn = false;
            var eraser = new SpecialEraser(_protectedLayers);
            eraser.ConfirmedEraseOneByOne(entIds);
        }
 
        [CommandMethod("ProtectedErase"CommandFlags.UsePickSet)]
        public static void DoProtectedErase()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var entIds = SelectEntities(ed);
            if (entIds == nullreturn;
 
            _overruleOn = false;
            var eraser = new SpecialEraser(_protectedLayers);
            eraser.ProtectedErase(entIds);
        }
 
        [CommandMethod("ProtectedEraseWithIds")]
        public static void DoProtectedEraseWithEntityIdsReturned()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var entIds = SelectEntities(ed);
            if (entIds == nullreturn;
 
            _overruleOn = false;
 
            var eraser = new SpecialEraser(_protectedLayers);
            var ids = eraser.ProtectedEraseWithEntityIds(entIds);
            if (ids.Count > 0)
            {
                var msg = 
                    $"{ids.Count} entit{(ids.Count > 1 ? "ies" : "y")} " +
                    $"{(ids.Count > 1 ? "is" : "are")} protected from erasing." +
                    "\n\nDo you really want to erase?";
                if (MsgBox.ShowYesNo(msg) ==
                    System.Windows.Forms.DialogResult.Yes)
                {
                    SpecialEraser.DoErase(ids);
                }
            }
        }
 
        #endregion
 
        #region CommandMethods: Turn on/off EraseOverrule against AutoCAD built-in erasing
 
        [CommandMethod("EraseOverrule")]
        public static void EnableEraseOverrule()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            if (!_overruleOn)
            {
                TurnOnEraseOverrule();
                _overruleOn = true;
                ed.WriteMessage("\nEraseOverule is turned on during command execution.\n");
            }
            else
            {
                TurnOffEraseOverrule();
                _overruleOn = false;
                ed.WriteMessage("\nEraseOverule is turned off.\n");
            }
        }
 
        #endregion
 
        #region private methods
 
        private static ObjectId[] SelectEntities(Editor ed)
        {
            var res = ed.GetSelection();
 
            if (res.Status != PromptStatus.OK)
            {
                ed.WriteMessage("\n*Cancel*\n");
                return null;
            }
            else
            {
                return res.Value.GetObjectIds();
            }
        }
 
        private static void TurnOnEraseOverrule()
        {
            EraseOverrule.Instance.Mode = EraseOverruleMode.GatherIds;
            EraseOverrule.Instance.Start(_protectedLayers);
 
            foreach (Document dwg in CadApp.DocumentManager)
            {
                dwg.CommandEnded += CommandEndedHandler;
            }
 
            CadApp.DocumentManager.DocumentCreated += (oe) =>
              {
                  e.Document.CommandEnded += CommandEndedHandler;
              };
 
            CadApp.DocumentManager.DocumentToBeDestroyed += (oe) =>
              {
                  e.Document.CommandEnded -= CommandEndedHandler;
              };
        }
 
        private static void CommandEndedHandler(object senderCommandEventArgs e)
        {
            if (!_overruleOn) return;
 
            var ids = EraseOverrule.Instance.EntitiesToErase;
            if (ids.Count > 0)
            {
                var cmd = e.GlobalCommandName;
                var msg = $"The command \"{cmd}\" attempted to erase {ids.Count} " +
                    $"protected entit{(ids.Count > 1 ? "ies" : "y")}.\n\n" +
                    "Do you really want to erase?";
 
                if (MsgBox.ShowYesNo(msg) == System.Windows.Forms.DialogResult.Yes)
                {
                    try
                    {
                        EraseOverrule.Instance.Stop();
 
                        var dwg = CadApp.DocumentManager.MdiActiveDocument;
                        using (dwg.LockDocument())
                        {
                            SpecialEraser.DoErase(ids);
                        }
                    }
                    finally
                    {
                        EraseOverrule.Instance.Start(_protectedLayers);
                    }
                }
 
                EraseOverrule.Instance.EntitiesToErase.Clear();
            }
        }
 
        private static void TurnOffEraseOverrule()
        {
            foreach (Document dwg in CadApp.DocumentManager)
            {
                dwg.CommandEnded -= CommandEndedHandler;
            }
 
            EraseOverrule.Instance.Stop();
        }
 
        #endregion
    }
}


As usual, following video clips shows how the code works. For simplicity, EraseOverrule is designed to only target entities on specific layers, in this video demo the layers are "MyLayer1" and "MyLayer2" with layer color as Yellow and Cyan. So, the 4 circles of Yellow/Cyan are the "protected entities, and can only be erased after user's confirmation, while other entities can be erased as usual. Each clip is corresponding to a command:

Clip 0 shows the drawing setup: 4 circles on "MyLayer1" and "MyLayer2", thus are "protected" by EraseOverrule, while other entities can be freely erased;
Clip 1 showing command "ConfirmedEraseInBatch";
Clip 2 showing command "ConfirmedEraseOneByOne";
Clip 3 showing command "ProtectedErase";
Clip 4 showing command "ProtectedEraseWithIds".

Review the code of each command before watching the corresponding video clip.

Again, while asking confirmation before erasing entities (or making other changes to entities, for that matter) is possible/doable, I'd be very careful of doing it and always try to avoid it unless it is a must-do.

The source code of entire project can be downloaded here, which is Visual Studio 2019/C# project against .NET Framework 4.8 and AutoCAD 2020 (I also noticed, even the DLL is compiled against AutoCAD 2020 .NET Assemblies, I have no problem NETLOAD the DLL into AutoCAD 2019 and run it).









Tuesday, December 27, 2016

Splitting Polyline/Curve With AutoCAD Civil 3D Labels Associated

In the era of using plain AutoCAD, I had written routines in AutoLISP and VBA to split a polyline into individual segment (e.g. breaking polyline at each vertex). After AutoCAD .NET API came, it is even easier to do this splitting: simply calling Curve.GetSplitCurves() will gives you back all the individual segment of a polyline/curve, based in the input points on the polyline/curve; in the case of polyline, most likely these points are the polyline's vertices.

However, once we moved from plain AutoCAD to some of the AutoCAD vertical products, in my case, it was AutoCAD Map, then AutoCAD Civil, thing can get complicated. 

With AutoCAD Map, the polylines, to which we used to do splitting, may have Object Data (a kind of attribute data that can be attached to AutoCAD entity, a bit similar to XData) attached. Once the usual splitting is done the attached data is gone, because the splitting is actually creating the individual segment of the polyline as new entities and the original polyline is erased.

With AutoCAD Civil, the splitting target polyline, besides possible Object Data being attached, may also be annotated with AutoCAD Civil label. In the case of Civil label, if the entity (polyline) being labelled is erased, the label will also be erased automatically. So, when my office moved from AutoCAD Map to AutoCAD Civil a while a go, I was asked to modify our polyline splitting tool (which have already handled the attached Object Data issue as aforementioned) to retain AutoCAD Civil label, if the polyline has been labelled.

Labelling is huge part of AutoCAD Civil 3D in terms of its customization. I am still pretty new on this. It took quite some study for me to figure out a working solution, be it is optimized or not. Since there is not as much programming resources available on AutoCAD Map/Civil3D, as on plain AutoCAD, I thought sharing my solution with fellow programmers would be good thing.

Here are the requirements:

1. Split a polyline (assume it is LWPolyline for the simplicity) into individual segment at each vertices;
2. If the polyline has been annotated with labels (assume the label is general segment line/curve label), the labels in the same label style remain.

Here are the logics of the solution:

1. Determine if the polyline is annotated with labels. If yes, what the 2 label styles (for line and curve segment) are used;
2. Get all individual segments and add them into database; 
3. Erase the polyline (then the labels annotating the polyline are gone);
4. Recreate labels on each individual segment with label style determined in Step 1.

Translating the logics into code:
using System.Collections.Generic;
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.PolylineSplitter))]
 
namespace Civil3DLabels
{
    public class PolylineSplitter
    {
        private const string DXF_LABEL = "AECC_GENERAL_SEGMENT_LABEL";
 
        [CommandMethod("SplitPoly")]
        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;
 
            // If the polyline is annotated with general segmen
            // label, find the label styles (line label and curve label),
            //If not labels, both label styles are ObjectId.Null.
            CadDb.ObjectId lineLabelStyleId;
            CadDb.ObjectId curveLabelStyleId;
            GetCurveGeneralSegmentLabelStyles(
                polyId, out lineLabelStyleId, out curveLabelStyleId);
            bool hasLabel = !lineLabelStyleId.IsNull && !curveLabelStyleId.IsNull;
 
            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 (label would also be gone, if exist
                        poly.UpgradeOpen();
                        poly.Erase(true);
                    }
                    else
                    {
                        ents[0].Dispose();
                    }
                }
 
                //Add label to each newly added entities
                if (newIds.Count>0 && hasLabel)
                {      
                    AddGeneralSegmentLabels(
                        newIds, lineLabelStyleId, curveLabelStyleId);
                }
 
                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 void AddGeneralSegmentLabels(
            IEnumerable<CadDb.ObjectId> featureIds, 
            CadDb.ObjectId lineLabelStyleId, 
            CadDb.ObjectId curveLabelStyleId)
        {
            foreach (var featureId in featureIds)
            {
                var labelId = CivilDb.GeneralSegmentLabel.Create(
                    featureId, 0.5, lineLabelStyleId, curveLabelStyleId);
            }
        }
 
        private void GetCurveGeneralSegmentLabelStyles(
            CadDb.ObjectId curveId,
            out CadDb.ObjectId lineStyleId, 
            out CadDb.ObjectId curveStyleId)
        {
            bool hasLabel = false;
            lineStyleId = CadDb.ObjectId.Null;
            curveStyleId = CadDb.ObjectId.Null;
 
            var id1 = CadDb.ObjectId.Null;
            var id2 = CadDb.ObjectId.Null;
 
            using (var tran = curveId.Database.TransactionManager.StartTransaction())
            {
                var ent = (CadDb.Entity)tran.GetObject(curveId, 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)
                        {
                            if (label.FeatureId == curveId)
                            {
                                if (!hasLabel) hasLabel = true;
 
                                if (id1.IsNull)
                                {
                                    id1 = label.StyleId;
                                }
                                else if (label.StyleId != id1)
                                {
                                    id2 = label.StyleId;
                                }
                            }
                        }
                    }
 
                    if (!id1.IsNull && !id2.IsNull) break;
                }
 
                //Only go further when at least one style is found
                if (hasLabel)
                {
                    if (!id1.IsNull)
                    {
                        //if it is Line Segment Style or Curve Segment Style
                        var civilDoc = CivilApp.ActiveDocument;
                        var lineStyles = 
                            civilDoc.Styles.LabelStyles.GeneralLineLabelStyles;
                        var curveStyles = 
                            civilDoc.Styles.LabelStyles.GeneralCurveLabelStyles;
 
                        if (IsTheLineSegmentStyle(id1, lineStyles, tran))
                        {
                            lineStyleId = id1;
                            if (!id2.IsNull)
                            {
                                if (IsTheLineSegmentStyle(id2, curveStyles, tran))
                                {
                                    curveStyleId = id2;
                                }
                            }
                        }
                        else
                        {
                            if (IsTheLineSegmentStyle(id1, curveStyles, tran))
                            {
                                curveStyleId = id1;
                                if (!id2.IsNull)
                                {
                                    if (IsTheLineSegmentStyle(id2, lineStyles, tran))
                                    {
                                        lineStyleId = id2;
                                    }
                                }
                            }
                        }
                    }
 
                    // The annotated polyline may only have line label or curve label
                    // So, here is to get default label style (either line label style
                    // or curve label style
                    CadDb.ObjectId defaultLineStyleId;
                    CadDb.ObjectId defaultCurveStyleId;
                    GetSettingsCmdAddSegmentLabelDefaultStyles(
                        out defaultCurveStyleId, out defaultLineStyleId);
 
                    if (lineStyleId.IsNull) lineStyleId = defaultLineStyleId;
                    if (curveStyleId.IsNull) curveStyleId = defaultCurveStyleId;
                }
 
                tran.Commit();
            }
        }
 
        private bool IsTheLineSegmentStyle(
            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 = 
                            IsTheLineSegmentStyle(styleId, styleIds, tran);
                    }
                }
 
                if (found) break;
            }
 
            return found;
        }
 
        private void GetSettingsCmdAddSegmentLabelDefaultStyles(
            out CadDb.ObjectId curveLabelStyleId, 
            out CadDb.ObjectId lineLabelStyleId)
        {
            var settings = CivilApp.ActiveDocument.Settings;
            var cmdSettings = 
                settings.GetSettings<Civil.Settings.SettingsCmdAddSegmentLabel>();
 
            lineLabelStyleId = cmdSettings.Styles.LineLabelStyleId.Value;
            curveLabelStyleId = cmdSettings.Styles.CurveLabelStyleId.Value;
        }

        #endregion
    }
}

In the code, the critical portion of the code is the private method GetCurveGeneralSegmentLabelStyle(), which does these things:

1. Check if the curve to be split has general segment labels associated or not.
2. If there is general segment labels associated, what the 2 label styles (line label and curve label) are.
3. Since the curve's split-able segments could be only lines, or only curves, we may only find one label style (line label style or curve label style) that is associated to the split-able curve.
4. The reason that I need to find both line and curve label styles, even the split-able curve may only use one label style, is that I need to re-create general segment labels on each split segment with Autodesk.Civil.DatabaseServices.GeneralSegmentLabel.Create(ObjectId, double, ObjectId, ObjectId), instead of Autodesk.Civil.DatabaseServices.GeneralSegmentLabel.Create(ObjectId, double). That is, I use the Create() method with 2 label styles' ObjectId passed in, instead of the Create() method that uses current default general segment label styles, set in Autodesk.Civil.Settings.SettingsCmdAddSegmentLable.Styles, which may be different from the original label styles found with the split-able curve.
5. Each of the 2 possible label styles (line or curve label styles) could have one or more child label styles, and each of the child styles could also have child styles...and so on. So the recursive method in the code to identify label style to be used.

This video clip shows the result of running the code.

The code is just a simplified way to re-label the individual segments after splitting a curve, it may not  re-create the label exactly as the original ones. For example, an original label could have been dragged along the segment from default location (centre of the segment), while the code shown here always create the label at the segment centre (the double argument of the Create() method is passed with value 0.5). So, to make the re-created label locate at exact location as the original one, when searching associated labels to the split-able curve, I would need to save the property value of GeneralSegmentLabel class of each found label of each segment, and use the value later when re-creating the label. It would need quite some extra code. I choose to leave it outside of this post.

Adittion Note

I also realize that a segment of line/curve could be annotated with multiple labels (in different label styles, of course). So, the code I posted here only works with segment being labelled with one label style. Obviously, if I want to retain all the labels in the case of multiple labels used on segments, I would need to identify all the labels that is associated to each segment before the splitting target polyline is erased. I'll see if I can find time to work out that part code later.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.