Attaching Data to AutoCAD Entity Objects31 Oct, 2000 By: Bill Kramer
Do you know what differentiates AutoCAD from a drawing program of lesser value? The quick answer is AutoCAD has programming interfaces. With programming you can automate tasks involved in the drawing and editing process. This, however, is a surface response with a much more complex answer that I will now explain. AutoCAD is a vehicle to a different type of computer drawing that involves creating smart drawings. Smart drawings are more than just lines, arcs and circles. Smart drawings are lines, arcs and circles that take on other properties based on the context of their use; that is, if a circle represents a drill hole, it can contain other attributes such as the type of drill hole, ID number of the hole and maybe even what is expected to go through the hole in the form of fasteners and other assembly items. The key is to attach data of our own design to drawing objects so they can be retrieved at a later time by our applications.
AutoCAD 2000 provides several ways that data can be attached directly to AutoCAD objects in the drawing. Let's look at each of them quickly and then explore the best solution for AutoCAD 2000 programmers.
Extended data has been supported in AutoCAD for several releases and continues to provide a mechanism that works well. The only real problem with extended data is that it is somewhat cumbersome to manipulate. The programmer and the AutoCAD system both have extra work to do in order to support extended data. Extended data is stored as part of the entity data itself. This means that it also presents limits to how much data can be stored because each entity can only use up so much space as defined in the AutoCAD database structure and memory model. Extended data is easiest to work with when using a set of utilities such as described in previous Programmer's Toolbox columns (See September 1996, "Saving Your Variables," pp. 89-98, and March 1994, "Extended Data Manipulations," pp. 99-105).
Another method of associating data with graphics is to use attributes. This mechanism is easier to implement compared to extended data because it entails basic command sequences or program list manipulations. The choice is yours based on comfort levels in programming and exactly what you hope to accomplish. Attributes are text data items that can appear as part of the drawing as text objects or can be invisible to the operator. But attributes can only be used with blocks, and individual objects such as lines, arcs and circles can't contain any ambient intelligence. Blocks with attributes work best when defining complete components such as a facilities-management system that uses blocks representing desks, chairs, tables and so forth.
The above two methods of associating non-graphic data in a drawing have been available to AutoLISP programmers for several years, and, through the use of utilities and experience, many have mastered them. It was through these utilities and AutoCAD's new object-based structure (that started in Release 13, got a lot better in Release 14 and is great in AutoCAD 2000) that a newer, better way evolved.
The Newer, Better Way
AutoCAD 2000 provides objects that are non-graphical containers capable of housing data. These objects are called dictionaries. From a programming perspective, access to a dictionary is similar to accessing a block definition. That is, you access the dictionary collection or table, then step down to the dictionary of interest and finally access the specific entry you seek. At first glance this seems to be even more cumbersome to program than extended data; however, Visual LISP has provided a tool for AutoLISP programmers that makes things simple. These are the (VLAX-LDATA-*) functions. Programmers call these the LDATA (LISP Data) functions.
Internally, LDATA is a collection of data dictionary records from which each record is accessed by a key name. Dictionaries are stored in a section of the drawing database different from the entities, one that is better optimized to locate and manipulate randomly structured data using a keyed reference. In the entity there is just a reference to the dictionary object. Performance for the entity is not compromised, and no limit exists as to how much data you can attach to it. To top it off, it is easier to attach LISP data such as lists of data. The LDATA functions make the job of attaching data to entities easy, as you will see in the next few paragraphs.
To enable the (VLAX-*) functions you must first run the (VL-LOAD-COM) function. This function will load a series of ObjectARX modules that expand Visual LISP into ActiveX support, object manipulations and much more. You only need to run (VL-LOAD-COM) once. Should you happen to run the function multiple times, it will immediately return since the modules will already have been loaded into memory. Many (VLAX) functions become available to you after the (VL-LOAD-COM) function runs. I'll only be looking at the (VLAX-LDATA-*) functions at this time.
The (VLAX-LDATA-LIST) will return a list of all the LDATA attached to an object. The object can be an entity name in which case the function returns any dictionary data that may be attached to the entity. This function is useful when working with data that may be stored with multiple keys. Our example functions, which I'll discuss later, will make extensive use of the list function to obtain all the associated data for a given entity object.
To attach data to an object, use the function (VLAX-LDATA-PUT). LDATA dictionary data requires a key, which should be unique within a given entity object to keep your data properly stored. The key is a string defined by your program application, so you control the uniqueness of the key value. If you use a key that already exists in the LDATA for an object, the previously stored data is overwritten with the new data.
Retrieve specific data by using a key and the function (VLAX-LDATA-GET). Whatever LISP data was stored with the object will be returned in the same form that it was saved. So, saving an integer with (VLAX-LDATA-PUT) will result in an integer return for (VLAX-LDATA-GET) given the same key name. The key is also used in (VLAX-LDATA-DELETE) to remove a specific LDATA value attached to an object.
Entity objects can act as hosts to LDATA by simply adding the data using (VLAX-LDATA-PUT). An example code sequence shows it best. Suppose you have the entity name for some drawn object in the variable EN and you want to attach some data to it. The following expression will attach the string "Excellent" to the entity EN using the key "Tryit".
(vlax-ldata-put EN "Tryit" "Excellent")
If you save the drawing then restart AutoCAD, you can retrieve the data stored using the same key against the same entity. Of course you need to activate the (VL-LOAD-COM) before we can use the (VLAX-LDATA-*) function set. (VL-LOAD-COM) may take a few seconds to run as it loads the extended VLAX library set.
LDATA manipulation is very easy for programmers when compared to attributes and extended data. The added time of loading the (VLAX) functions is not much and the functions are easy to use. If you have ever worked with extended data, you know what I mean when I say that the LDATA approach is much easier.
Consider the following dialog with the AutoCAD command system; it shows just how easy it is to add a list containing different data types as LDATA to an entity.
Command: (setq alist (list "A" 0 2.5 (list 0.1 0.2 0.3) "B"))
("A" 0 2.5 (0.1 0.2 0.3) "B")
Command: (vlax-ldata-put (car (entsel)) "Trythis" alist)
Select object: //pick any entity
("A" 0 2.5 (0.1 0.2 0.3) "B")
Command: (vlax-ldata-get (car (entsel)) "Trythis")
Select object: //pick same entity
("A" 0 2.5 (0.1 0.2 0.3) "B")
From the programmer's perspective, LISP data remains with the objects when stored to disk. You can add LDATA to all sorts of objects including the drawing itself. This data is available when the drawing is reloaded into the AutoCAD drawing editor and your program restarted. Many applications can make use of this feature to save and restore their current state as drawings are worked on in multiple sessions. You could also use LDATA to keep track of entities in a drawing. As an example, you can create a drawing reactor that adds LDATA to objects as they are added to the drawing for tracking purposes. This LDATA could be used to keep track of valid objects in the drawing, links to other objects, intelligence about what the object represents and much more. The imagination is the only limit for programmers using these kinds of tools.
One thing to keep in mind when using LDATA to link data objects with other entity data objects is that you should not store entity names. You should use entity handles instead, as they will not change as the drawing is saved and reloaded. Entity names will change as the drawing is reloaded. The same constraints exist with selection sets. A group collection is a better way to store a selection set if you need to keep it from one drawing session to the next. These same constraints exist when working with extended data and attributes as well.
Let's take a look at a set of utilities that demonstrate the ease with which LDATA can be integrated into your applications. These utilities add data tags to entities. This example is greatly simplified and represents the basis from which more grandiose applications can be created. For example, the data tags might be comments from reviewers of the drawing, a tracking system to monitor proper drawing edit work or text from an assembly system building data chains of its own design.
Listing 1 (see bottom of this page or download CDNC8-00.LSP)contains a utility function that will add LDATA to a drawing object. The LDATA you are adding is a simple list that contains the current time date value from the drawing system (a real number), an operator name and a comment string. For this application, entities can have any number of LDATA comment lists added. So, you used a base string plus an incrementing number for the LDATA key names. That way you are always adding a new uniquely keyed member to the LDATA dictionary associated with the entity object. The function in Listing 2 calls this function using a base string of "TagData", thus the keys used for a single entity object will be "TagData0", "TagData1", "TagData2" and so on.
In Listing 1 the function (TagObject) starts by obtaining a list of the LDATA already attached to the entity object EN using (VLAX-LDATA-LIST). An incrementing integer (INC) is initially set to a zero value, and the function starts a simple loop to find a new key name. In the test portion of the WHILE loop the base name string is concatenated to the increment value and then used as the key in an ASSOC expression to search the LDATA list. When a key match is made, the loop iterates incrementing the value of INC for another attempt. At the end of the loop the variable INC contains a number that when concatenated to the base string will form a new unique key name for the LDATA.
Note that the function (VLAX-LDATA-LIST) returns the LDATA in an association list format with the key string being the first member of each sub-list. Thus searching the LDATA list is simply a matter of using the ASSOC function with the key name. It does not get much easier from an AutoLISP programming perspective.
With the unique key name created from the base string and value in INC, the (VLAX-LDATA-PUT) expression is used to attach the list data containing the system date/time, user name and comment string to the entity. The function returns the data just added as a result of the put operation.
The function in Listing 2 is a command (C:) function called C:TAG that can be used to demonstrate the TagObject function from Listing 1. The TAG function asks for the user to select an object, then it asks for the user's name and comment to supply to the function TagObject. When an object is tagged, the function ends normally. If the object is not tagged because there was no input for the name and comment, a prompt is issued telling the operator that the object was not tagged.
Listing 3 demonstrates how to retrieve the tag data stored as LDATA. The command function SEETAG asks for an entity object to be selected. It then retrieves the LDATA attached using the (VLAX-LDATA-LIST) function. A loop runs through each of the LDATA items and checks to see if the key name matches the "TagData" string style used in the TagObject function. When a match is found, the tag data is printed to the screen.
A more robust version of the SEETAG function can be found at the CADENCE Web site (www.cadenceweb. com). The Web example will go through the entire drawing and print the values of any tagged objects it locates. It can be used as a template to build your own attribute extraction function for your own use of LDATA entity attachments.
Visual LISP has many tools that greatly expand the ability LISP programmers have in customizing AutoCAD. Many, like the LDATA functions, perform specific tasks useful to programmers. LDATA entity attachments are just one example of the many new tools provided in the enhanced Visual LISP environment. Hopefully you can take this information and apply it right away in your custom applications. You should be using AutoCAD 2000 with Visual LISP for proper results. Until next time, keep on programmin'.
|Listing 1. Tag Data on Object|
;; (defun TagObject (EN ;;entity name Base ;;tag name base OpNam ;;operator name Comment ;;comment string / Inc ;;increment Dat ;;existing ldata ) ;; ;; get existing ldata and save in DAT. ;; (setq Dat (vlax-ldata-list EN) Inc 0) ;; ;; Search ldata attached to object to find ;; next available Base string + Inc key. ;; (while (assoc (strcat Base (itoa Inc)) Dat) (setq Inc (1+ Inc))) ;; ;; Attach ldata to drawing object ;; (vlax-ldata-put EN ;;entity to attach data (strcat Base (itoa Inc)) ;;key (list (getvar "DATE") ;;data list OpNam Comment )) )
|Listing 2. Function to Test TagObject Function|
;; (defun C:TAG () (setq EN (entsel "\nSelect an object: ")) (if EN (progn (setq EN (car EN) OpNam (getstring 1 "\nName: ") Comment (getstring 1 "\nComment: ")) (if (and EN (/= OpNam "") (/= Comment "")) (TagObject EN "TagData" OpNam Comment) (prompt "\nObject not tagged.") ) ) ) (princ) )
|Listing 3. Function to View Tags|
;; (defun C:SEETAG () (setq EN (entsel "\nSelect an object: ")) (if EN (progn (setq EN (car EN) TMP (vlax-ldata-list EN) ) (for each Item TMP (if (= (substr (car Item) 1 7) "TagData") (print Item))) ) ) (princ) )