Search code examples
arrayslistlispautocadautolisp

Autolisp entity data retrieval


I'm trying out autocad and I want to build a "highway" between a rectangle and a line. I need the 2 points from the rectangle. Any ideas?

(setq en(car(entsel"\Get rectangle : ")))
 (entget en)

My whole code

    (defun temaLisp(/  )
  ;HIGHWAY BUILDER
  ;My project is a highwaybulder
  ;How does it work?
  ;Select a rectangle and draw from there to a distance the highway where it meets a stop(Line)
      (princ "TemaLisp ")
   ;get rectangle (prompt "\nSelect the ends of a station")
  (setq en(car(entsel"\Get rectangle : ")))
  (entget en)
  ;get the stop (Line)


     (setq line2 (car (entsel "\nSelect the second line: ")))
            (setq p3 (cdr (assoc 10 (entget line2))))
            (setq p4 (cdr (assoc 11 (entget line2))))

  ;of the highway &optional (size 50)
  (setq mid1 (midpt pt3 pt4)) ; midpoint for dotted line

  ; Draw the lines
  (command "line" mid1 mid2)
)       

Solution

  • In AutoCAD, rectangles (created using the AutoCAD RECTANG command) are represented using closed 2D lightweight polyline (LWPOLYLINE) entities.

    An LWPOLYLINE entity contains the following DXF data:

    (
        (-1 . <Entity name: 7ffff706880>)  ;; Pointer to self
        (0 . "LWPOLYLINE")                 ;; Entity Type
        (330 . <Entity name: 7ffff7039f0>) ;; Point to parent
        (5 . "FFF")                        ;; Handle
        (100 . "AcDbEntity")               ;; Class
        (67 . 0)                           ;; Tilemode
        (410 . "Model")                    ;; Layout
        (8 . "0")                          ;; Layer
        (100 . "AcDbPolyline")             ;; Subclass
        (90 . 4)                           ;; Vertices
        (70 . 1)                           ;; Bitwise flag (1=Closed)
        (43 . 0.0)                         ;; Constant width
        (38 . 0.0)                         ;; Elevation
        (39 . 0.0)                         ;; Thickness
        (10 18.9133 17.6315)               ;; Vertex coordinate (OCS)
    
        < ... additional vertex data ... >
    
        (10 18.9133 12.7863)               ;; Vertex coordinate (OCS)
        (40 . 0.0)                         ;; Segment starting width
        (41 . 0.0)                         ;; Segment ending width
        (42 . 0.0)                         ;; Segment bulge
        (91 . 0)                           ;; Vertex identifier
        (210 0.0 0.0 1.0)                  ;; Extrusion (normal) vector
    )
    

    Here, the 2D OCS coordinates of each vertex are stored using a DXF group 10 entry in the DXF data.

    There are many ways to obtain a list of values held by multiple occurrences of a DXF group within DXF data (hence obtaining a list of vertices of the polyline).

    Since the assoc AutoLISP function returns the first occurrence of a key within an association list, I shall refer to these functions as massoc function (i.e. multiple assoc).


    1. foreach

    (defun massoc1 ( key lst / rtn )
        (foreach x lst
            (if (= key (car x))
                (setq rtn (cons (cdr x) rtn))
            )
        )
        (reverse rtn)
    )
    

    This first example simply iterates over every item in the supplied association list, and if the content of the address register (car) of the item is equal to the desired key, the value associated with the key (or content of the decrement register - cdr) is added to the list returned by the function.

    This list is reversed before being returned since the list is being constructed in reverse, with each item pushed onto the front of the list - this is far more efficient than using a combination of append/list to build the list in order.


    2. append / mapcar

    (defun massoc2 ( key lst )
        (apply 'append
            (mapcar
                (function
                    (lambda ( x ) (if (= key (car x)) (list (cdr x))))
                )
                lst
            )
        )
    )
    

    An alternative way of iterating over the list, however, since mapcar returns the result of the evaluation of the supplied function for each list item, those items which do not meet the criteria if the if statement will result in nil appearing in the list returned by `mapcar.

    These nil values are removed by exploiting the duality of nil and the empty list () in AutoLISP, by applying the append function to append all sublists & nil values present in the list returned by mapcar.


    3. vl-remove-if-not

    (defun massoc3 ( key lst )
        (mapcar 'cdr
            (vl-remove-if-not
                (function (lambda ( x ) (= key (car x))))
                lst
            )
        )
    )
    

    As it says on the tin: items are removed if the predicate function supplied to the vl-remove-if-not function returns nil (vl-remove-if could also be used with a negated predicate function) - hence items whose first element is not equal to the desired key are removed.

    The mapcar function is then used to return the value associated with each association list item returned by vl-remove-if-not.


    4. while / assoc / member

    (defun massoc4 ( key lst / itm rtn )
        (while (setq itm (assoc key lst))
            (setq rtn (cons (cdr itm) rtn) lst (cdr (member itm lst)))
        )
        (reverse rtn)
    )
    

    This method is far more efficient than those which precede it, as the assoc & member functions are used to jump straight to the target items in the supplied list, rather than iterating over & testing each and every item.

    assoc returns the first occurrence of a key within an association list, and member returns the tail of a list with the first item equal to the supplied argument.

    In this way, the assoc function retrieves the target item, the member function returns the rest of the list starting at this item, and the list is repeatedly redefined to contain all items following this target item through the use of cdr.


    5. recursive / assoc / member

    (defun massoc5 ( key lst / itm )
        (if (setq itm (assoc key lst))
            (cons (cdr itm) (massoc5 key (cdr (member itm lst))))
        )
    )
    

    A variation on the above, however, in this case, rather than redefining the list for each item found, the remainder of the list is passed as an argument to a recursive evaluation of the function.


    6. acet-list-m-assoc (Express Tools)

    (defun massoc6 ( key lst )
        (mapcar 'cdr (acet-list-m-assoc key lst))
    )
    

    This version of the function makes use of the acet-list-m-assoc function defined as part of the Express Tools library provided as an optional addition to full versions of AutoCAD.

    But this is cheating! :-)


    7. Basic recursion

    (defun massoc7 ( key lst )
        (if lst
            (if (= key (caar lst))
                (cons (cdar lst) (massoc7 key (cdr lst)))
                (massoc7 key (cdr lst))
            )
        )
    )
    

    This final example is essentially a recursive version of the foreach example demonstrating above. The function simply looks at the first item in the supplied list, and, if the first element matches the key argument, it is cons'd to the list returned by the recursive call with the remainder of the list, else the remainder of the list is passed to the recursive call without an item added to the return.


    Examples

    Now that we have discussed the various ways in which such a function may be defined, how should such a function be used?

    Each of the above functions accepts two arguments: a 'key' and an association list. This is syntactically the same as the standard AutoLISP assoc function.

    Such a function could be used to obtain all values associated with DXF group 10 within a DXF association list, using the syntax:

    (massoc 10 <dxf-data>)
    

    For example (at the Visual LISP IDE console):

    ;; Obtain a LWPOLYLINE entity
    _$ (setq ent (car (entsel)))
    <Entity name: 7ffff706880>
    
    ;; Retrieve the DXF data
    _$ (setq dxf (entget ent))
    ((-1 . <Entity name: 7ffff706880>) (0 . "LWPOLYLINE") ... (91 . 0) (210 0.0 0.0 1.0))
    
    ;; Obtain the values associated with all DXF group 10 entries
    _$ (massoc 10 dxf)
    ((13.0161 12.4807) (25.727 12.4807) (25.727 18.6426) (13.0161 18.6426))
    

    This could be used in a sample program in the following way:

    (defun c:test ( / dxf ent )
        (if
            (and
                (setq ent (car (entsel "\nSelect rectangle: ")))
                (setq dxf (entget ent))
                (= "LWPOLYLINE" (cdr (assoc 0 dxf)))
            )
            (print (massoc 10 dxf))
        )
        (princ)
    )
    
    (defun massoc ( key lst / rtn )
        (foreach x lst
            (if (= key (car x))
                (setq rtn (cons (cdr x) rtn))
            )
        )
        (reverse rtn)
    )
    

    Performance Considerations

    Where performance is concerned, the above variations of the same function are not equal - those which iterate over every item in the supplied list are less efficient than those which 'skip' directly to the target items using in-built functions such as assoc and member.

    As a quick comparison, consider the following benchmark results:

    ;;;Benchmarking ................Elapsed milliseconds / relative speed for 32768 iteration(s):
    ;;;
    ;;;    (MASSOC5 2 L).....1482 / 1.25 <fastest> ;; recursive/assoc/member
    ;;;    (MASSOC4 2 L).....1482 / 1.25           ;; while/assoc/member
    ;;;    (MASSOC6 2 L).....1498 / 1.24           ;; acet-list-m-assoc
    ;;;    (MASSOC3 2 L).....1638 / 1.13           ;; vl-remove-if-not
    ;;;    (MASSOC7 2 L).....1747 / 1.06           ;; basic recursion
    ;;;    (MASSOC1 2 L).....1748 / 1.06           ;; foreach
    ;;;    (MASSOC2 2 L).....1856 / 1 <slowest>    ;; append/mapcar
    

    As expected, the assoc/member functions prove to be the fastest, with the Express Tools function a close second.