Nate Holt's Blog

September 26, 2009

Nate’s Simple AutoLISP – Lesson 006

Filed under: Tutorials — nateholt @ 3:17 pm

Experimenting with attribute definition order, entity sorting, and entmake

This lesson’s issue: Scrambled attribute order on a block insert.

(Note: previous postings: Lesson 001, Lesson 002, Lesson 003Lesson 004, and Lesson 005)

Here is an AutoCAD Electrical transformer symbol popped into a circuit. If I decide to use the generic attribute editor command built into AutoCAD to examine and possibly adjust values for this instance of the transformer block insert, the attribute order listed is pretty scrambled.

autolisp006a

It appears that the order that attributes are listed here are in the same order that the ATTDEF entities were inserted into the block’s source “.dwg” file. (Note: you can use AutoCAD’s BATTMAN command and painfully re-order the attribute listing for the block reference in the active drawing… but that is a one-time fix and not a permanent library “.dwg”-based solution. Read on.)

Call up the block’s original “.dwg” file, VXF1D.dwg. Type this at the command prompt:

Command: (entget (car (entsel))) [Enter]  

Select object:[Pick on the TAG1 attribute definition]

((-1 . <Entity name: 7ffffbbedf0>) (0 . "ATTDEF") (330 . <Entity name: 7ffffbbe820>) (5 . "5F")(100 . "AcDbEntity") (67 . 0) (410 . "Model")
(8 . "0") (100 . "AcDbText") (10 0.166667 0.749995 0.0) (40 . 0.125) (1 . "XF") (50 . 0.0) (41 . 1.0) (51 . 0.0) (7 . "WD") (71 . 0) (72 . 1)
(11 0.375 0.749995 0.0) (210 0.0 0.0 1.0) (100 . "AcDbAttributeDefinition") (280 . 0) (3 . "") (2 . "TAG1") (70 . 0) (73 . 0) (74 . 0) (280 . 0))

and then pick on the TAG1 attribute definition. This expression will read the attribute definition entity (the one you select) and return the entity’s data (above). This is pretty cryptic but the part shown in red is the picked entity’s “handle” number, “5F”.

If we repeat for the RATING1 and the DESC1 attributes, the handle numbers are “5E” and “60” respectively.

autolisp006b

These entity handle numbers (in hexidecimal format) indicate the order that these attribute definitions were created and added to the library symbol drawing. It looks like the RATING1 attribute definition (5E) was created just before the TAG1 (5F) attribute definition. Next, the DESC1 (60) was defined and added to the symbol.

ATTDEF creation order and attribute display order

Does the creation order drive the attribute’s display order when the source library symbol is inserted as a block insert into another drawing?

Let’s take another look at the attribute editor dialog display when the symbol above is inserted as a block reference into a drawing.

autolisp006c

Note that these three attributes are listed together with RATING1 (original handle “5E”) leading, TAG1 next (original handle “5F”) and DESC1 (original handle “60”) bring up the rear. It looks like that the creation order DOES influence the display order.

Let’s say that we really want the TAG1 attribute to be first in the list. Then we want the three DESC1, DESC2, and DESC3 attributes listed, then all of the “RATING*” attributes, catalog assignment attributes, and then all the rest in alphabetical order. So, if creation order is a controlling factor, we have a couple options…

OPTION 1: We could carefully re-construct our VXF1D.dwg base library symbol (and the many hundreds of others) so that the attribute defintions are each created in this order.

OPTION 2: Write a little AutoLISP utility that takes an existing scrambled library symbol dwg file, deletes all the attribute definitions, and then pushes them back into the symbol’s dwg file in the desired order.

Let’s go with the second option.

Using (entmake…)

Let’s try a little experiment. Let’s save an ATTDEF’s block of data into an AutoLISP variable. Then delete the ATTDEF using AutoCAD ERASE command. Then see if we can re-create a new, carbon-copy of the original ATTDEF with a call to “entmake”.

1. With our VXF1D.dwg drawing open on the screen, type this at the command prompt:

Command: (setq tag1_data (entget(car (entsel)))) [Enter, pick on TAG1 attdef]

Select object: ((-1 . <Entity name: 7ffffbbedf0>) (0 . “ATTDEF”) (330 . <Entity
name: 7ffffbbe820>) (5 . “5F”) (100 . “AcDbEntity”) (67 . 0) (410 . “Model”) (8
. “0”) (100 . “AcDbText”) (10 0.166667 0.749995 0.0) (40 . 0.125) (1 . “XF”)
(50 . 0.0) (41 . 1.0) (51 . 0.0) (7 . “WD”) (71 . 0) (72 . 1) (11 0.375
0.749995 0.0) (210 0.0 0.0 1.0) (100 . “AcDbAttributeDefinition”) (280 . 0) (3
. “”) (2 . “TAG1”) (70 . 0) (73 . 0) (74 . 0) (280 . 0))

Command: (setq desc1_data (entget (car (entsel)))) [Enter, pick on DESC1 attdef]

Select object: ((-1 . <Entity name: 7ffffbbee00>) (0 . “ATTDEF”) (330 . <Entity
name: 7ffffbbe820>) (5 . “60”) (100 . “AcDbEntity”) (67 . 0) (410 . “Model”) (8
. “0”) (100 . “AcDbText”) (10 0.234887 -0.89792 0.0) (40 . 0.125) (1 . “”) (50
. 0.0) (41 . 0.7) (51 . 0.0) (7 . “WD”) (71 . 0) (72 . 1) (11 0.424471 -0.89792
0.0) (210 0.0 0.0 1.0) (100 . “AcDbAttributeDefinition”) (280 . 0) (3 . “”) (2
. “DESC1”) (70 . 0) (73 . 0) (74 . 0) (280 . 0))

Command: (setq rating1_data (entget (car (entsel)))) [Enter, pick on RATING1 attdef]

Select object: ((-1 . <Entity name: 7ffffbbede0>) (0 . “ATTDEF”) (330 . <Entity
name: 7ffffbbe820>) (5 . “5E”) (100 . “AcDbEntity”) (67 . 0) (410 . “Model”) (8
. “0”) (100 . “AcDbText”) (10 0.136875 0.59375 0.0) (40 . 0.125) (1 . “”) (50 .
0.0) (41 . 0.7) (51 . 0.0) (7 . “WD”) (71 . 0) (72 . 1) (11 0.399375 0.59375
0.0) (210 0.0 0.0 1.0) (100 . “AcDbAttributeDefinition”) (280 . 0) (3 . “”) (2
. “RATING1”) (70 . 0) (73 . 0) (74 . 0) (280 . 0))

2. Now erase these three ATTDEFS ( ! )

3. Now, let’s recreate them, but in a specific order. Type these three commands at the command line:

(entmake tag1_data) [Enter]

(entmake desc1_data) [Enter]

(entmake rating1_data) [Enter]

4. The deleted ATTDEFs are re-created, but they are now in a different order. Confirm that the handles of the three new ATTDEFs now increment in the new order of TAG1, DESC1, and then RATING1.

 

A Re-order ATTDEF AutoLISP utility

With our little utility, we need to more or less duplicate what we just did above:  1. Capture all the ATTDEFs, 2. Erase them, 3. Re-create them with (entmake…) in the desired order.

Here’s what our little AutoLISP utility’s program flow might look like in order to do this:

1. We first define the order for specific attribute definition tagnames. We decide that we’ll support wild-cards in the tag names. This makes it easy to define all of the various AutoCAD Electrical component “tag-ID” attribute tagnames in the first entry of the priority list.

  ; Set up order list of ATTDEFS. Wild-cards are supported.     
  (setq orderlst (list "TAG1*,TAG2,TAGSTRIP,P_TAG1,P_TAGSTRIP"
                       "DESC1"
                       "DESC2"
                       "DESC3"
                       "RATING*"
                       "MFG"
                       "CAT"
                       "ASSYCODE"
                       "INST"
                       "LOC"
                       "FAMILY"
                       "TERM*"))

2. Now, with the source block “.dwg” open in AutoCAD, our utility constructs a selection set of all ATTDEF entities found in the drawing. The call is to “ssget” and we pass the “_X” parameter which processes ALL entities in the active drawing. Our expression has a ‘((0 . “ATTDEF”)) filter included so that only ATTDEFs will end up in our selection set.

  ; Gather up selection set of all ATTDEFs on the active drawing
  (setq ss (ssget "_X" '((0 . "ATTDEF"))))

3. Our utility then opens each of these ATTDEF entities in a “while” loop, indexing through the ATTDEFs in the selction set “ss”, one at a time. It pushes each ATTDEF block of data into an overall big list of data that we’re calling “attdef_lst”.

   (if (AND (/= ss nil)(> (sslength ss) 1))
    (progn ; at least one ATTDEF found. Okay to continue.
      ; Extract all ATTDEF tag names and overall ATTDEF entity data. Save in parallel lists.     
      (setq attdef_lst nil) ; start with blank data list
      (setq slen (sslength ss)) ; number of ATTDEFs in selection set
      (setq ix 0) ; used to index through the selection list
      (while (< ix slen)        
        (setq en (ssname ss ix)) ; get next ATTDEF entity name from selection set
        (setq ed (entget en)) ; open up this ATTDEF entity
        (setq attdef_lst (cons ed attdef_lst)) ; add its data to the big list
        (princ " ")(princ (cdr (assoc 2 ed))) ; display existing order in command window
        (setq ix (1+ ix)) ; increment index to get next ATTDEF entity from selection set
      )

4. Next we sort this long list of chunks of ATTDEF data alphabetically – by the tagname part of each chunk of ATTDEF data. Setting up this sort function is a bit cryptic. See the normal AutoCAD help for a few more examples. For our time here, just go with it as shown. Basically it doing a sort on the “2” part of each ATTDEF block of data. This is the part that carries the ATTDEF “tag name”. To get at it in each list, our function uses the “(assoc 2…)” to dig it out and then a “(cdr …” to strip off the leading “2” identifier. This leaves just the tag name to be used in the sort compare.

      ; Initially sort the list alphabetically by tagname. The tagname is in the "2"
      ; part of the entity's data. Access the value by extracting with the
      ; (assoc 2 ...) function.
      (setq attdef_lst (vl-sort attdef_lst
            '(lambda (X Y) (< (cdr (assoc 2 X)) (cdr (assoc 2 Y))))))

5. Now we’re ready to create a new version of this data list with certain ATTDEF tagnamed chunks pushed to the front part of the list. The utility cycles through the tagname priority list that we’ve set up in “orderlst” (step #1 above). For each tagname or wild-carded tagname, the utility cycles through the alphabetized list, looking for a match on the (cdr (assoc 2 …)) tagname extracted from each ATTDEF block of data. On a match, that block of data is pushed into the new list.

      ; Okay. So far, so good. We now have all ATTDEFs alphabetically sorted and held
      ; in a big data list called "attdef_lst".
           
      ; Now prepare to create a brand new ATTDEF list by pulling entries from the
      ; "attdef_lst" and pushing them into this new list in the desired order.
      (setq new_attdef_lst nil) ; Start with empty new list
      (setq processed_ix_lst nil) ; To be used for tracking what items have been
                                  ; pulled from alphabetized list into the new list.
     
      ; Process the ATTDEF tagnames against the desired order given in the "orderlst"
      (foreach sortorder_name orderlst
        (setq ix 0)
        (foreach rec attdef_lst
          (setq attdef_tag (cdr (assoc 2 rec))) ; extract ATTDEF tagname for this entry
          (if (AND (not (member ix processed_ix_lst)) ; check if already processed
                   (wcmatch attdef_tag sortorder_name)) ; check if tagname wild-card
                                      ; matches with the target sortordername string.
            (progn ; This ATTDEF name matches what we're looking for. Pull its data out
                   ; of the attdef_lst and push the entity's block of data into the
                   ; new "new_attdef_lst" ordered list.
              (setq new_attdef_lst (cons rec new_attdef_lst))
              ; Remember this entry's index number so won't try to add it in again
              (setq processed_ix_lst (cons ix processed_ix_lst))
          ) )
          (setq ix (1+ ix))
        )
      )   

6. When no more of the priority list to process, we need to push any un-referenced attribute definition chunks of data on to the new list (they are still alphabetized).

      ; The new_attdef_lst should contain the ordered attributes that were specifically
      ; defined. Now add in any left-overs. These will go in alphabetically.
      (setq ix 0)
      (foreach rec attdef_lst
        (if (not (member ix processed_ix_lst))
          ; This entry not processed above. Go ahead and push it into the new list.
          (setq new_attdef_lst (cons rec new_attdef_lst))
        )
        (setq ix (1+ ix))
      )

7.  That’s it. Our new ATTDEF blocks of data list is complete. But it’s in reverse order because we used the more efficient “cons…” function to build the new version of the list (instead of using the slower “append…” function). Reverse the list.

      ; Now put the new_attdef_lst into the correct order (it is reversed due to
      ; using "(cons ...) to add elements to the beginning of the list instead of
      ; the end of the list)
      (setq new_attdef_lst (reverse new_attdef_lst))

8. Now we cross our fingers. The utility erases ALL of the existing ATTDEF entities in the drawing…

      ; Erase all existing ATTDEFS (!)
      (command "_.ERASE" ss "")

9. … and we then cycle through our new, re-ordered list of ATTDEF data and re-create each ATTDEF using calls to (entmake …) .

      ; Now rewrite the ATTDEFs but using the new ordered list of ATTDEF blocks of
      ; data. Each ATTDEF should pop back in exactly as before, but the handle assignment
      ; given to each re-written ATTDEF will now be the next sequential for the
      ; drawing (!)
      (foreach rec new_attdef_lst
        (princ "\n")(princ (cdr (assoc 2 rec))) ; display attdef name to command window
        (entmake rec)
      )

 

Full Utility

Here’s the full utility. Cut and paste it into a file called reorder_attdef.lsp.

; ----------  R E O R D E R _ A T T D E F . L S P  ---------------------
(defun c:reorder_attdef ( / attdef_lst ix slen ss en ed attdef_tag new_attdef_lst
                            processed_ix_lst rec orderlst sortorder_name x y)
                           
  ; Set up order list of ATTDEFS. Wild-cards are supported.     
  (setq orderlst (list "TAG1*,TAG2,TAGSTRIP,P_TAG1,P_TAGSTRIP"
                       "DESC1"
                       "DESC2"
                       "DESC3"
                       "RATING*"
                       "MFG"
                       "CAT"
                       "ASSYCODE"
                       "INST"
                       "LOC"
                       "FAMILY"
                       "TERM*"))
  ; Gather up selection set of all ATTDEFs on the active drawing
  (setq ss (ssget "_X" '((0 . "ATTDEF"))))
 
  (if (AND (/= ss nil)(> (sslength ss) 1))
    (progn ; at least one ATTDEF found. Okay to continue.
      ; Extract all ATTDEF tag names and overall ATTDEF entity data. Save in parallel lists.     
      (setq attdef_lst nil) ; start with blank data list
      (setq slen (sslength ss)) ; number of ATTDEFs in selection set
      (setq ix 0) ; used to index through the selection list
      (while (< ix slen)        
        (setq en (ssname ss ix)) ; get next ATTDEF entity name from selection set
        (setq ed (entget en)) ; open up this ATTDEF entity
        (setq attdef_lst (cons ed attdef_lst)) ; add its data to the big list
        (princ " ")(princ (cdr (assoc 2 ed))) ; display existing order in command window
        (setq ix (1+ ix)) ; increment index to get next ATTDEF entity from selection set
      )
     
      ; Initially sort the list alphabetically by tagname. The tagname is in the "2"
      ; part of the entity's data. Access the value by extracting with the
      ; (assoc 2 ...) function.
      (setq attdef_lst (vl-sort attdef_lst
            '(lambda (X Y) (< (cdr (assoc 2 X)) (cdr (assoc 2 Y))))))
      ; Okay. So far, so good. We now have all ATTDEFs alphabetically sorted and held
      ; in a big data list called "attdef_lst".
           
      ; Now prepare to create a brand new ATTDEF list by pulling entries from the
      ; "attdef_lst" and pushing them into this new list in the desired order.
      (setq new_attdef_lst nil) ; Start with empty new list
      (setq processed_ix_lst nil) ; To be used for tracking what items have been
                                  ; pulled from alphabetized list into the new list.
     
      ; Process the ATTDEF tagnames against the desired order given in the "orderlst"
      (foreach sortorder_name orderlst
        (setq ix 0)
        (foreach rec attdef_lst
          (setq attdef_tag (cdr (assoc 2 rec))) ; extract ATTDEF tagname for this entry
          (if (AND (not (member ix processed_ix_lst)) ; check if already processed
                   (wcmatch attdef_tag sortorder_name)) ; check if tagname wild-card
                                      ; matches with the target sortordername string.
            (progn ; This ATTDEF name matches what we're looking for. Pull its data out
                   ; of the attdef_lst and push the entity's block of data into the
                   ; new "new_attdef_lst" ordered list.
              (setq new_attdef_lst (cons rec new_attdef_lst))
              ; Remember this entry's index number so won't try to add it in again
              (setq processed_ix_lst (cons ix processed_ix_lst))
          ) )
          (setq ix (1+ ix))
        )
      )   
     
      ; The new_attdef_lst should contain the ordered attributes that were specifically
      ; defined. Now add in any left-overs. These will go in alphabetically.
      (setq ix 0)
      (foreach rec attdef_lst
        (if (not (member ix processed_ix_lst))
          ; This entry not processed above. Go ahead and push it into the new list.
          (setq new_attdef_lst (cons rec new_attdef_lst))
        )
        (setq ix (1+ ix))
      )
      ; Now put the new_attdef_lst into the correct order (it is reversed due to
      ; using "(cons ...) to add elements to the beginning of the list instead of
      ; the end of the list)
      (setq new_attdef_lst (reverse new_attdef_lst))
     
      ; Erase all existing ATTDEFS (!)
      (command "_.ERASE" ss "")
     
      ; Now rewrite the ATTDEFs but using the new ordered list of ATTDEF blocks of
      ; data. Each ATTDEF should pop back in exactly as before, but the handle assignment
      ; given to each re-written ATTDEF will now be the next sequential for the
      ; drawing (!)
      (foreach rec new_attdef_lst
        (princ "\n")(princ (cdr (assoc 2 rec))) ; display attdef name to command window
        (entmake rec)
      )
  ) )
  (setq ss nil) ; release the selection set
  (princ)
)        
       

Let’s try it.

With drawing VXF1D.dwg open on screen, APPLOAD the above utility file reorder_attdef.lsp.

Then type REORDER_ATTDEF [Enter] at the command prompt. In the blink of an eye, the utility should delete all ATTDEFs, sort, and re-write them. Save the symbol “.dwg”.

Now, in a fresh or purged drawing, insert this revised symbol. Cross fingers. Launch the attribute edit command, pick on the newly inserted transformer symbol…

autolisp006d

Good work! Now batch this tool against a bunch of symbols (make a backup first, just in case!). In short order your library should be modified.

Advertisement

Could some custom applications make your company more productive? Find out. http://n8consultants.com

U P D A T E :  I just noticed that the original program with “RATING*” would put RATING10, 11, and 12 right after RATING1 and before RATING2. Looks like my original attribute definition order list example should explicitly list all 12 “RATING*” tag names like this to avoid this problem:

(setq orderlst (list "TAG1*,TAG2,TAGSTRIP,P_TAG1,P_TAGSTRIP" 
                       "DESC1" 
                       "DESC2" 
                       "DESC3" 
                       "RATING1" 
                       "RATING2" 
                       "RATING3" 
                       "RATING4"
                       "RATING5"
                       "RATING6"
                       "RATING7"
                       "RATING8"
                       "RATING9"
                       "RATING10"
                       "RATING11"
                       "RATING12"
                       "MFG" 
                       "CAT" 
                       "ASSYCODE" 
                       "INST"
                       "LOC"
                       "FAMILY"
                       "TERM*"))

3 Comments »

  1. […] Simple AutoLISP lesson 006 – attdefs, order, entity sorting, enmake […]

    Pingback by Index of AutoCAD Electrical Utilities – April 2006 through August 2009 « AutoCAD Electrical Etcetera — September 28, 2009 @ 8:20 pm

  2. I am running into an error that comes up when I close close in the APPLOAd dialog. It says its loaded successfully, but the non the ccommand line I get ; error: malformed list on input. I have commented out my additions and ran the base lisp. It still errors..

    Comment by Pat Corun — March 28, 2022 @ 4:27 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.