📜 ⬆️ ⬇️

AutoCAD: Communication with external data

Often, users want to associate drawing objects with external data — an Excel spreadsheet, a database, or simply a text or xml file. Of course, in AutoCAD, there are many ways to interact with external data: _DataLink, dbConnect, attin, _script. But all these tools require “many clicks” and do not provide the necessary interaction. The user wants the drawing objects to be automatically aligned with the external data, with minimal participation. An exemplary example of such interaction is FDO in AutoCAD Map 3D - when, after connecting to the source, we immediately receive its content as graphics (and, if desired, even with signatures) and to synchronize with the source, it is enough to update the layer (one command). But this is a special (and therefore limited) GIS tool that is not suitable for everyone. And then it remains only to apply programming, the benefit there is a huge number of ways to do it - Lisp, C ++, .Net, Delphi, Python, etc.

One way to establish such a link to a data source is to create a class that will be responsible for linking to data or simply aggregate this data for import / export. In this article I will show an example of this class, which:

  1. Draws itself (but not Custom object *)
  2. Has its own data and monitors their relevance.
  3. Can import / export itself to XML

* Custom object is a ObjectARX functional that allows you to create your own graphic primitives, but they are implemented as separate libraries, and if they are not present, the Custom object turns into a proxy object pumpkin . Objects of vertical solutions AutoDESK is a Custom object and, in order to see them in the “bare” AutoCAD, you need to set the corresponding Object Enabler.

In AutoCAD, a programmer has several tools for storing his own data in a drawing, these are XData and XRecord. But these are very limited tools and access to this data is only available from AutoCAD. Therefore, we will use an external data source, and connect them with primitives in the drawing, via Handle.
')
Our object will draw a circle, as well as a text label containing the radius of this circle, but this method allows you to create any primitives (for example, Solid3d or NurbSurface) - if only they had a Handle. We will need methods to draw the Object and Tags. But most importantly, we need a method to track the change of the object, an event handler that we hang on the Modified event. We also need methods for updating primitives and exporting to XML. Well, enough, fewer words - more code.

Class mycircle
Imports System.Xml Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.EditorInput Imports Autodesk.AutoCAD.Geometry Public Class MyCircle Private fCenter As Point3d Private fRadius As Double Private OID As ObjectId Private textID As ObjectId Public Sub New(cp As Point3d, r As Double, db As Database) Me.fCenter = cp Me.fRadius = R ' Me.DrawMe(db) Me.DrawLebel() End Sub Public Sub New(MyCircleData As XmlElement, db As Database) Dim wHandle As New Handle(Long.Parse(MyCircleData.GetAttribute("Handle"), Globalization.NumberStyles.HexNumber)) OID = New ObjectId Me.fCenter = Me.ParsePoint(MyCircleData.GetAttribute("Center")) Me.fRadius = MyCircleData.GetAttribute("Radius") If db.TryGetObjectId(wHandle, OID) Then Me.UpgradeMe() Else Me.DrawMe(db) End If Me.DrawLebel() End Sub Public Sub UpgradeMe() Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim wDBObj As Circle = OID.GetObject(OpenMode.ForWrite) RemoveHandler wDBObj.Modified, AddressOf CirMod wDBObj.Radius = Me.fRadius wDBObj.Center = Me.fCenter wDBObj.UpgradeOpen() AddHandler wDBObj.Modified, AddressOf CirMod acTrans.Commit() End Using End Sub Public Sub UpgradeLabel() Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim acText As DBText = textID.GetObject(OpenMode.ForWrite) acText.Position = Me.fCenter acText.TextString = Me.fRadius acText.UpgradeOpen() acTrans.Commit() End Using End Sub Public Sub DrawMe(db As Database) Using acTrans As Transaction = db.TransactionManager.StartTransaction() Dim cNewCircle As New Circle(Me.fCenter, New Vector3d(0, 0, 1), Me.fRadius) '    (    ) Dim btrCurrSpace As BlockTableRecord = acTrans.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) '       OID = btrCurrSpace.AppendEntity(cNewCircle) acTrans.AddNewlyCreatedDBObject(cNewCircle, True) AddHandler cNewCircle.Modified, AddressOf CirMod '  acTrans.Commit() End Using End Sub Public Sub DrawLebel() If textID.IsNull Then Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim acBlkTbl As BlockTable acBlkTbl = acTrans.GetObject(OID.Database.BlockTableId, OpenMode.ForRead) Dim acBlkTblRec As BlockTableRecord acBlkTblRec = acTrans.GetObject(acBlkTbl(BlockTableRecord.ModelSpace), OpenMode.ForWrite) Dim acText As New DBText() acText.SetDatabaseDefaults() acText.Position = Me.fCenter acText.Height = 2 acText.TextString = Me.fRadius textID = acBlkTblRec.AppendEntity(acText) acTrans.AddNewlyCreatedDBObject(acText, True) acTrans.Commit() End Using End If End Sub Public Sub EraseLebel() If Not textID.IsNull Then Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim wDBObj As DBText = textID.GetObject(OpenMode.ForWrite) wDBObj.Erase() wDBObj.UpgradeOpen() acTrans.Commit() End Using End If End Sub Public Sub CirMod(ByVal senderObj As Object, ByVal evtArgs As EventArgs) Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim wDBObj As Circle = OID.GetObject(OpenMode.ForRead) Me.fCenter = wDBObj.Center Me.fRadius = wDBObj.Radius UpgradeLabel() acTrans.Commit() End Using End Sub Private Function ParsePoint(wStr As String) As Point3d wStr = wStr.Replace("(", "") wStr = wStr.Replace(")", "") Dim Arr() As String = wStr.Split(",") Return New Point3d(Double.Parse(Arr(0)), Double.Parse(Arr(1)), Double.Parse(Arr(2))) End Function Public Property Center As Point3d Get Return Me.fCenter End Get Set(value As Point3d) Me.fCenter = value Me.UpgradeMe() End Set End Property Public Sub Print(wEditor As Editor) Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() wEditor.WriteMessage("Handle: " & OID.Handle.ToString & Environment.NewLine) Dim wDBObj As Circle = OID.GetObject(OpenMode.ForRead) wEditor.WriteMessage("Radius: " & wDBObj.Radius & Environment.NewLine) wEditor.WriteMessage("Center: " & wDBObj.Center.ToString & Environment.NewLine) End Using End Sub Public Function ToXML(wDoc As XmlDocument) As XmlElement Dim res As XmlElement = wDoc.CreateElement("MyCircle") Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() res.SetAttribute("Handle", Me.OID.Handle.ToString) Dim wDBObj As Circle = OID.GetObject(OpenMode.ForRead) res.SetAttribute("Radius", wDBObj.Radius) res.SetAttribute("Center", wDBObj.Center.ToString) End Using Return res End Function End Class 


A few explanations:

 Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.EditorInput Imports Autodesk.AutoCAD.Geometry 

Did you remember to connect cmgd.dll, acdbmgd.dll, accoremgd.dll (AcAd> = 2013)?

  Private fCenter As Point3d Private fRadius As Double 

The object's own data, yes, these are properties of a primitive, but this is an example.

  Private OID As ObjectId ' Private textID As ObjectId ' 

References to drawing primitives.

  Public Sub DrawMe(db As Database) Using acTrans As Transaction = db.TransactionManager.StartTransaction() Dim cNewCircle As New Circle(Me.fCenter, New Vector3d(0, 0, 1), Me.fRadius) '    (    ) Dim btrCurrSpace As BlockTableRecord = acTrans.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) '       OID = btrCurrSpace.AppendEntity(cNewCircle) acTrans.AddNewlyCreatedDBObject(cNewCircle, True) AddHandler cNewCircle.Modified, AddressOf CirMod '  acTrans.Commit() End Using End Sub 

We draw the object itself, and at the same time we remember the reference to the primitive and, most importantly, we hang the handler to change the object.

  Public Sub CirMod(ByVal senderObj As Object, ByVal evtArgs As EventArgs) Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() 'Dim acText As DBText = textID.GetObject(OpenMode.ForWrite) Dim wDBObj As Circle = OID.GetObject(OpenMode.ForRead) Me.fCenter = wDBObj.Center Me.fRadius = wDBObj.Radius UpgradeLabel() acTrans.Commit() End Using End Sub 

We process the change of the object - we obtain new values ​​of the properties of the object, we update the Label.

  Public Sub DrawLebel() If textID.IsNull Then Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() Dim acBlkTbl As BlockTable acBlkTbl = acTrans.GetObject(OID.Database.BlockTableId, OpenMode.ForRead) Dim acBlkTblRec As BlockTableRecord acBlkTblRec = acTrans.GetObject(acBlkTbl(BlockTableRecord.ModelSpace), OpenMode.ForWrite) Dim acText As New DBText() acText.SetDatabaseDefaults() acText.Position = Me.fCenter acText.Height = 2 acText.TextString = Me.fRadius textID = acBlkTblRec.AppendEntity(acText) acTrans.AddNewlyCreatedDBObject(acText, True) acTrans.Commit() End Using End If End Sub 

Draw object label.

  Public Sub New(MyCircleData As XmlElement, db As Database) Dim wHandle As New Handle(Long.Parse(MyCircleData.GetAttribute("Handle"), Globalization.NumberStyles.HexNumber)) OID = New ObjectId Me.fCenter = Me.ParsePoint(MyCircleData.GetAttribute("Center")) Me.fRadius = MyCircleData.GetAttribute("Radius") If db.TryGetObjectId(wHandle, OID) Then Me.UpgradeMe() Else Me.DrawMe(db) End If Me.DrawLebel() End Sub Public Function ToXML(wDoc As XmlDocument) As XmlElement Dim res As XmlElement = wDoc.CreateElement("MyCircle") Using acTrans As Transaction = OID.Database.TransactionManager.StartTransaction() res.SetAttribute("Handle", Me.OID.Handle.ToString) Dim wDBObj As Circle = OID.GetObject(OpenMode.ForRead) res.SetAttribute("Radius", wDBObj.Radius) res.SetAttribute("Center", wDBObj.Center.ToString) End Using Return res End Function 

Import / export to XML.

Well, of course, we need teams in AutoCAD to work with our class.

Command class
 Imports Autodesk.AutoCAD.Runtime Imports AppServ = Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.EditorInput Imports Autodesk.AutoCAD.Geometry Imports System.Windows.Forms Imports System.Xml Public Class CommandClass Dim wList As List(Of MyCircle) = Nothing <CommandMethod("CrMyCircle")> _ Public Sub CrMyCircle() If wList Is Nothing Then wList = New List(Of MyCircle) Dim acDoc As AppServ.Document = AppServ.Application.DocumentManager.MdiActiveDocument Dim acCurDb As Database = acDoc.Database Dim pPtRes As PromptPointResult = acDoc.Editor.GetPoint(" : ") If (pPtRes.Status = PromptStatus.OK) Then Dim wPrmtDistOpt As New PromptDistanceOptions(" : ") wPrmtDistOpt.BasePoint = pPtRes.Value wPrmtDistOpt.UseBasePoint = True Dim pDistRes As PromptDoubleResult = acDoc.Editor.GetDistance(wPrmtDistOpt) If (pDistRes.Status = PromptStatus.OK) Then wList.Add(New MyCircle(pPtRes.Value, pDistRes.Value, acCurDb)) End If End If End Sub <CommandMethod("SaveToXML")> _ Public Sub SaveToXML() Dim nDialog As New SaveFileDialog nDialog.Filter = "XML|*.xml" Dim wDoc As New XmlDocument wDoc.LoadXml("<?xml version=""1.0"" encoding=""utf-8""?><MyCircleList/>") If wList IsNot Nothing Then wList.ForEach(Sub(obj) wDoc.DocumentElement.AppendChild(obj.ToXML(wDoc))) If nDialog.ShowDialog = DialogResult.OK Then wDoc.Save(nDialog.FileName) End If End Sub <CommandMethod("LoadFromXML")> _ Public Sub LoadFromXML() Dim nDialog As New OpenFileDialog nDialog.Filter = "XML|*.xml" Dim wDoc As New XmlDocument Dim done As Boolean = False If nDialog.ShowDialog = DialogResult.OK Then wDoc.Load(nDialog.FileName) done = True End If If done Then If wList Is Nothing Then wList = New List(Of MyCircle) Dim acDoc As AppServ.Document = AppServ.Application.DocumentManager.MdiActiveDocument Dim acCurDb As Database = acDoc.Database For Each ch In wDoc.DocumentElement.ChildNodes wList.Add(New MyCircle(ch, acCurDb)) Next End If End Sub <CommandMethod("DrawLabel")> _ Public Sub DrawLabel() If wList IsNot Nothing Then wList.ForEach(Sub(obj) obj.DrawLebel()) End Sub <CommandMethod("EraseLabel")> _ Public Sub EraseLabel() If wList IsNot Nothing Then wList.ForEach(Sub(obj) obj.EraseLebel()) End Sub End Class 


  <CommandMethod("CrMyCircle")> _ Public Sub CrMyCircle() If wList Is Nothing Then wList = New List(Of MyCircle) Dim acDoc As AppServ.Document = AppServ.Application.DocumentManager.MdiActiveDocument Dim acCurDb As Database = acDoc.Database Dim pPtRes As PromptPointResult = acDoc.Editor.GetPoint(" : ") If (pPtRes.Status = PromptStatus.OK) Then Dim wPrmtDistOpt As New PromptDistanceOptions(" : ") wPrmtDistOpt.BasePoint = pPtRes.Value wPrmtDistOpt.UseBasePoint = True Dim pDistRes As PromptDoubleResult = acDoc.Editor.GetDistance(wPrmtDistOpt) If (pDistRes.Status = PromptStatus.OK) Then wList.Add(New MyCircle(pPtRes.Value, pDistRes.Value, acCurDb)) End If End If End Sub 

The CrMyCircle command draws our objects.

  <CommandMethod("SaveToXML")> _ Public Sub SaveToXML() Dim nDialog As New SaveFileDialog nDialog.Filter = "XML|*.xml" Dim wDoc As New XmlDocument wDoc.LoadXml("<?xml version=""1.0"" encoding=""utf-8""?><MyCircleList/>") If wList IsNot Nothing Then wList.ForEach(Sub(obj) wDoc.DocumentElement.AppendChild(obj.ToXML(wDoc))) If nDialog.ShowDialog = DialogResult.OK Then wDoc.Save(nDialog.FileName) End If End Sub 

Save to XML.

  <CommandMethod("LoadFromXML")> _ Public Sub LoadFromXML() Dim nDialog As New OpenFileDialog nDialog.Filter = "XML|*.xml" Dim wDoc As New XmlDocument Dim done As Boolean = False If nDialog.ShowDialog = DialogResult.OK Then wDoc.Load(nDialog.FileName) done = True End If If done Then If wList Is Nothing Then wList = New List(Of MyCircle) Dim acDoc As AppServ.Document = AppServ.Application.DocumentManager.MdiActiveDocument Dim acCurDb As Database = acDoc.Database For Each ch In wDoc.DocumentElement.ChildNodes wList.Add(New MyCircle(ch, acCurDb)) Next End If End Sub 

Load from XML.

Conclusion

Just like that, it is not easy to create a class that is able to associate drawing data with data from an external source (XML). It is quite easy to modify it, for interacting with an external database.

Source: https://habr.com/ru/post/271109/


All Articles