While code is rather simple and quite self-explanatory, I also placed a bit comments here and there.
These are 2 major classes:
using System.Collections.Generic; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; namespace CatchmentLabelData { public class CatchmentAreaInfo { public string AreaName { set; get; } = ""; public double Area { set; get; } = 0.0; public double Coefficient { set; get; } = 1.0; public double ConcentrationTime { set; get; } = 0.0; public override string ToString() { return AreaName + "," + Area.ToString("#####0.000") + "," + Coefficient.ToString("#0.00") + "," + ConcentrationTime.ToString("##0.0"); } } public class CatchmentLabelDataExtractor { public Document ThisDwg { private set; get; } public void ExtractDataFromLabels(Document dwg) { ThisDwg = dwg; IEnumerable<CatchmentAreaInfo> data = ExtractData(); if (data!=null) { SaveData(data); } else { CadApp.ShowAlertDialog( "No catchment data found!"); } } #region private methods: extract data from Catchment labels private IEnumerable<CatchmentAreaInfo> ExtractData() { var lblIds = FindCatchmentLabels(); if (lblIds.Count == 0) return null; var data = new List<CatchmentAreaInfo>(); foreach (var id in lblIds) { CatchmentAreaInfo area = GetCatchmentAreaDataFromLabel(id); if(area!=null) { data.Add(area); } } return data.Count > 0 ? data : null; ; } private CatchmentAreaInfo GetCatchmentAreaDataFromLabel(ObjectId lblId) { CatchmentAreaInfo area = null; using (var tran = lblId.Database.TransactionManager.StartTransaction()) { var ent = (Entity)tran.GetObject(lblId, OpenMode.ForRead); var lblText = ExplodeLabel(ent); if (!string.IsNullOrEmpty(lblText)) { area = ParseLabelText(lblText); } tran.Commit(); } return area; } private string ExplodeLabel(Entity ent) { var lblText = ""; var exploded = new List<DBObject>(); // first round of explosion: the result is a BlockReference BlockReference blk = null; var outEnts = new DBObjectCollection(); ent.Explode(outEnts); foreach (DBObject obj in outEnts) { exploded.Add(obj); if (obj is BlockReference) { blk = (BlockReference)obj; } } // second round of explosion, the result is an MText if (blk!=null) { outEnts = new DBObjectCollection(); blk.Explode(outEnts); foreach (DBObject obj in outEnts) { exploded.Add(obj); if (obj is MText) { lblText = ((MText)obj).Text; } } } // disposed entities resulted in by explosion foreach (DBObject obj in exploded) { obj.Dispose(); } return lblText; } /// <summary> /// The text parsing logic here would heavily be depended on /// the label style's content /// </summary> /// <param name="lblText"></param> /// <returns></returns> private CatchmentAreaInfo ParseLabelText(string lblText) { var txts = lblText.Replace("\r\n", "|").Split('|'); if (txts.Length!=4) { // Invalid label content // Since we target particular label style // we should know the lable content has 4 pieces of data return null; } var name = txts[0]; var area = ParseForNumber(txts[1]); var coe = ParseForNumber(txts[2]); var time = ParseForNumber(txts[3]); CatchmentAreaInfo catchment = new CatchmentAreaInfo { AreaName = name, Area = area, Coefficient = coe, ConcentrationTime = time }; return catchment; } private double ParseForNumber(string txt) { double num = 0.0; int start = -1; int end = -1; for( int i=0; i<txt.Length; i++) { if (char.IsNumber(txt,i)) { if (start == -1) start = i; break; } } if (start>=0) { for (int i=start+1; i<txt.Length;i++) { if (!char.IsNumber(txt, i) && !char.IsPunctuation(txt,i)) { end = i; break; } } if (end <= 0) end = txt.Length; } if (start>=0 && end>start) { var s = txt.Substring(start, end - start).Trim(); num = double.Parse(s); } return num; } private List<ObjectId> FindCatchmentLabels() { var ids = new List<ObjectId>(); using (var tran = ThisDwg.TransactionManager.StartTransaction()) { var model = (BlockTableRecord)tran.GetObject( SymbolUtilityServices.GetBlockModelSpaceId(ThisDwg.Database), OpenMode.ForRead); foreach (ObjectId id in model) { //ThisDwg.Editor.WriteMessage($"\nEntity => {id.ObjectClass.DxfName}"); if (id.ObjectClass.DxfName.ToUpper()== "AECC_CATCHMENTAREA_LABEL") { //=========================================================== // we may want to further examine the label's LabelStyle // in order to make sure this label does contain the data // as expected according to the LabelStyle desgin. // for simplicity of this sample code, I skip it //=========================================================== ids.Add(id); } } tran.Commit(); } return ids; } #endregion #region private methods: save extracted data private void SaveData(IEnumerable<CatchmentAreaInfo> data) { var txts = new List<string>(); foreach (var catchment in data) { txts.Add(catchment.ToString()); } var header = "Name,Area,Runoff Coefficient,Time of Concentration\r\n"; var outputText = header + string.Join("\r\n", txts.ToArray()); var fileName = GetFileName(); if (!string.IsNullOrEmpty(fileName)) { if (System.IO.File.Exists(fileName)) { System.IO.File.Delete(fileName); } System.IO.File.WriteAllText(fileName, outputText); } } private string GetFileName() { var fileName = ""; using (var dlg = new System.Windows.Forms.SaveFileDialog()) { dlg.Title = "Save Catchment Area Information To CSV File"; if (ThisDwg.IsNamedDrawing) { dlg.InitialDirectory = System.IO.Path.GetDirectoryName( ThisDwg.Name); dlg.FileName = System.IO.Path.GetFileNameWithoutExtension( ThisDwg.Name) + ".csv"; } dlg.Filter = "CSV File *.csv|*.csv"; dlg.OverwritePrompt = true; if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { fileName = dlg.FileName; } } return fileName; } #endregion } }
Here is the CommandClass that runs process:
For simplicity, I let the output to be saved as CSV file. The primary goal of this article is about retrieving Catchment data from its label.
Some thoughts on this topic:
It is quite frustrating that Autodesk seems does not make much effort to update/enhance Civil 3D's API. Label is most powerful thing in Civil 3D: it can designed to automatically obtain data from Civil 3D objects (as LabelStyle's Content). Obviously, there is internal API to do this, because user can edit the label style's content. But these APIs are not exposed to outside at all (maybe, because they are too complicated. I am always amazed how labels in Civil 3D work).
Through this discussion, I can see, with lack of API support, one possible workaround of getting Civil 3D object information is to:
1. Define a label style to let its content expose the wanted information. That is, let Civil 3D to do the work of collecting data from its objects into particular labels;
2. We then can use similar approach shown in this article to obtain the data from labels.
The source code with built DLL files can be downloaded here. Note, if you want to run the DLL from this download without building from the project\s source code, you need to unlock it first (because of the executable DLL is downloaded via Internet). The project is built against Civil 3D 2020 and .NET Framework 4.72.
using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(CatchmentLabelData.MyCommands))] namespace CatchmentLabelData { public class MyCommands { [CommandMethod("CatchmentData")] public static void RunMyCommand() { var doc = CadApp.DocumentManager.MdiActiveDocument; var ed = doc.Editor; try { var extractor = new CatchmentLabelDataExtractor(); extractor.ExtractDataFromLabels(doc); } catch(System.Exception ex) { ed.WriteMessage( $"\nError: \n{ex.Message}\n"); } } } }
For simplicity, I let the output to be saved as CSV file. The primary goal of this article is about retrieving Catchment data from its label.
Some thoughts on this topic:
It is quite frustrating that Autodesk seems does not make much effort to update/enhance Civil 3D's API. Label is most powerful thing in Civil 3D: it can designed to automatically obtain data from Civil 3D objects (as LabelStyle's Content). Obviously, there is internal API to do this, because user can edit the label style's content. But these APIs are not exposed to outside at all (maybe, because they are too complicated. I am always amazed how labels in Civil 3D work).
Through this discussion, I can see, with lack of API support, one possible workaround of getting Civil 3D object information is to:
1. Define a label style to let its content expose the wanted information. That is, let Civil 3D to do the work of collecting data from its objects into particular labels;
2. We then can use similar approach shown in this article to obtain the data from labels.
The source code with built DLL files can be downloaded here. Note, if you want to run the DLL from this download without building from the project\s source code, you need to unlock it first (because of the executable DLL is downloaded via Internet). The project is built against Civil 3D 2020 and .NET Framework 4.72.
2 comments:
Very interesting post shared by you
Autocad training institute in Noida
Autocad training institute in Delhi
Such a wonderful post. Thanks for posting
Autocad training institute in Noida
Autocad training institute in Delhi
Post a Comment