Visual LISP: Attribute Magic In this edition we look at how you can manipulate attributes using Visual LISP®. Once you understand how attributes are stored in the drawing, the process is relatively simple. A tip of the wizard’s cap to “RP” for suggesting that we retrieve attribute-related spells from the chest to share with everyone. You will find an associated scroll in the Scrolls and Potions section of The Code Wizard's Laboratory for this article that helps you get started with attribute manipulation.
Blocks with variable string attributes are a useful method for storing data in a drawing and have been an important part of drawing with AutoCAD® software since the earliest releases. As a result, attributes and blocks play an integral part in many applications and AutoCAD installations. One of the key features of blocks and attributes is that the CAD/CAM user can manipulate them and can build an extraction file containing all the inserted attribute data relatively easily. You don’t have to know any programming to accomplish those tasks. And that is why blocks with attributes are a popular tool.
However, with programming you can accomplish magic! Attribute data can be changed automatically based on other data in the drawing or from an external source. With the information in this article, along with the utilities in the associated scroll, you can begin to exploit attributes in magical ways. Attribute Entity ObjectsBut first you need to understand more about the nature of attributes. An attribute entity object is where AutoCAD stores an attribute used in a drawing. There are two phases in the life of an attribute: definition and application.
To define an attribute you use the ATTDEF command in the AutoCAD drawing editor. This command creates an entity object known as an ATTDEF. From here on, when I use the term ATTDEF, I am referring to the entity object, not the AutoCAD command.
The ATTDEF entity object is used in blocks. It is added to a block when you define the contents of the block either by creating a drawing file that serves as a block in other drawing files or by using the AutoCAD BLOCK command and selecting the definition object. When you insert a block with ATTDEF objects into a drawing, the attribute entities are also inserted. Other system variables in AutoCAD such as ATTREQ, ATTDIA, and ATTMODE control how attributes are used. You’ll find more information about these variables in the AutoCAD help documentation if you are not familiar with what they offer.
Once an attribute definition object is part of a block and you have inserted the block into the drawing, another entity type is created, beginning the attribute’s application phase. In the application of an attribute, an ATTRIB entity object is created. ATTRIB objects exist in the drawing database immediately following the INSERT object they are associated with. Any number of ATTRIB objects can be found between the INSERT and SEQEND objects. | Figure 1: Insert Entity and ATTRIB Objects order in drawing. By using INSERT and SEQEND, you can attach any number of ATTRIB objects to a given block reference. |
| When processing attributes in Visual LISP, you normally start with the block reference entity name. The source of the entity name depends on the application in which the attribute is being accessed. One possible source is the user selecting an entity in your program via the entsel function or as part of a selection set using the ssget function. It is also possible to obtain an entity name if you have an entity handle. Entity handles are text strings assigned to each entity object to be used as references from external sources such as a database. Handles are converted to entity names using the handent function.
Given the entity name of an INSERT object that has attached attributes, you use entnext to move to the ATTRIB object that follows in the drawing database. To provide for a variable number of attributes, you typically place the entnext and entget (entity get) functions inside a loop that terminates when the SEQEND entity object is encountered.
The following code demonstrates the basic processing of an INSERT object with attributes. The entity name of the INSERT object is supplied in EN.
(setq EN (entnext EN) ;move from INSERT object to first ATTRIB EL (entget EN) ;get the entity list ) (while (/= (cdr (assoc 0 EL)) "SEQEND") ;loop while not end of sequence ; ;process attribute object ; (setq EN (entnext EN) ;move to next ATTRIB in chain EL (entget EN) ;get entity data list ) ) ;end of while loop
Although the CDR and ASSOC combination may seem a bit strange at first, it is frequently used when manipulating entity data lists. The preceding code shows the pair in use as part of the while loop test. Association lists are a convenient mechanism for storing data in Visual LISP.
When using CDR and ASSOC, keep in mind that not all blocks have attributes. To find out, you must check the INSERT object. The entity data list for an INSERT object includes a flag with a code number value of 66. If the associated value is 1 (one), then attributes follow. A setting of 0 (zero) means that the block has no attributes.
Thus you could use a test to make sure that the original entity name EN was for an INSERT entity object with attached ATTRIB objects. Test the group code value 66 in the entity data list for the original EN if you think the entity name may not reference a block with attributes. Entity Object DataNow let’s look at the attribute entity object data. The entity data list for an ATTRIB object appears much like that for a TEXT object. The data includes the text point, alignment control information, text style selection, text generation parameters, and the text itself, just as you find in the TEXT entity object. The difference is that the ATTRIB object has a tag, which is the name or reference of the attribute.
To access the tag string of an attribute, use the CDR ASSOC combination with a group value of 2 (two). You can use the same technique to access the data value for the attribute, but with group code 1 (one). You can learn about the other group codes in the online documentation for DXF™ and Visual LISP. For now, we concentrate only on the tag and value because those are the items that applications involving attributes use most frequently.
Suppose you want to write a routine that gets the value of an attribute, given a tag string and an entity name. To do so requires a few simple adjustments to the preceding code. In the “;process attribute object” area you add a test to see whether the tag string value of the entity data list equals that of the one you are seeking. If a match is found, then the data value is extracted. You use the CDR ASSOC pair to perform the basic data access from the entity data list.
Suppose the desired tag string is supplied as a variable named TAG. The following code tests the entity data (EL) to see if the tag string is equal and then sets the variable VAL to the attribute value.
(if (= (cdr (assoc 2 EL)) TAG) (setq VAL (cdr (assoc 1 EL))))
Putting this all together, you end up with the function ATT_GetOne, available in the Scrolls and Potions [URL TBA]. Given the entity name and a tag name, the function ATT_GetOne returns the string value of an attribute. If the tag name is not part of the attribute set, the result is nil.
After locating the appropriate entity data list, changing an attribute value in the drawing database requires only two more steps. First, use the list substitution subr subst to change the text in the value entry (group code 1) of the ATTRIB entity data list. The key is to remember that the item you are substituting must be a dotted pair just like the value in the entity data list. Once the substitution has been completed, send the updated entity data list back to the AutoCAD drawing database. Let’s take a closer look at these two steps.
In the first step you need to substitute an association list member in an entity list. That means building a dotted pair just like those found in an entity data list. Dotted pairs are lists created using the cons function, where the second parameter supplied is an atom and not a list. Normally you use the cons function to add items to the front of an existing list. But when cons is used with two atoms (primitive types), the result is a dotted pair. Thus the expression
(cons 1 "A string")
produces the dotted pair that the entity data list requires:
(1 . "A string")
Suppose the new value for the attribute is stored with the variable NEW. The following expression replaces the existing value for group code one (1), the attribute value in EL, with NEW.
(setq EL (subst (cons 1 NEW) (assoc 1 EL) EL))
The expression (cons 1 NEW) creates a new dotted pair to replace the existing one in EL. Note that only the ASSOC was used to retrieve the old setting. That is because you are substituting the entire dotted pair and not just the value. Even though the code value (first part of the dotted pair) does not change, you must work with the dotted pair as a dotted pair so that Visual LISP recognizes which item in the list you are manipulating.
Now you need to send the updated entity data list back to the drawing. For most entities you can do this by calling the entmod function with the revised entity data list. Although this does update the drawing database, it does not update the screen display when you use nested objects such at attributes. To update the entity, you must use the entupd function. The entupd function uses the entity name and regenerates just that entity. When working with complex objects—ones that require more than a single entity object for storage—you need to use the entupd function. That way you can make many changes to the same object group and then tell AutoCAD to update the display. Because you control when the system changes the graphics, execution is faster. The ATT_PutOne FunctionNow let’s look at another utility function, ATT_PutOne, also provided with comments in the Scrolls and Potions section of The Code Wizard’s Laboratory.
(defun ATT_PutOne (EN TAG NEW / EL) (setq EN (entnext EN) EL (entget EN)) (while (and (= (cdr (assoc 0 EL)) "ATTRIB") (/= (cdr (assoc 2 EL)) TAG)) (setq EN (entnext EN) EL (entget EN))) (if (= (cdr (assoc 0 EL)) "ATTRIB") (progn (entmod (subst (cons 1 NEW) (assoc 1 EL) EL)) (entupd EN))))
The structure of the ATT_PutOne function is similar to what we have been looking at in this article. The entity name of the INSERT object is supplied in EN. You start by getting the next entity object that is expected to be an attribute entity object. You use a loop to read through the entity objects, checking to see whether the entity data list contains an ATTRIB entity object and whether the tag is not equal to the one you seek. The loop stops if the entity data list does not contain an ATTRIB object or if the tag name is equal to the one supplied in TAG.
After the while loop stops, an if expression checks whether EL holds an ATTRIB object. If so, the loop terminates because the TAG match was found and not because you ran out of attribute objects. If an attribute is found, the entmod function is supplied with a modified entity data list created using the subst function. Once the entmod function completes, the entupd function updates the entity on the display.
The Scrolls and Potions section for attribute manipulation contains several useful utilities for working with attributes. You can use them to write programs that read and update attribute entity data in a drawing. And since you already know that attributes represent a powerful tool for AutoCAD users, just imagine the magic you can do with them now! |