Search code examples
lispautocadautocad-pluginautolisp

AutoLISP - Change Layers Automatically


I want to make a simple AutoLISP routine where I draw lines in layer "0" and afterwards change back to the original layer.

With the code below, I can draw a line but it will stay in the current layer. If I let out the last code line, I draw the line in layer "0" but afterwards there is no change back to the original layer.

(defun c:testfunction (/ OLD)
  (setq OLD (getvar 'clayer))
  (setvar 'clayer "0")
  (command "line")
  (setvar 'clayer OLD)
)

Concept of the Code

I first store the current layer in the OLD variable, then I change the layer to "0". After the LINE command I change back to the OLD layer.

Thank you in advance.


Solution

  • Pausing for Input

    Since the AutoCAD LINE command can issue an arbitrary number of prompts to the user (depending on the number of lines they wish to draw), you'll need to include a loop within the code to allow for an arbitrary number of user inputs, before resetting the current layer.

    Your current code will evaluate the expression (setvar 'clayer OLD) before the user has supplied the first line point, as there is no pause for user input.

    To pause for user input in an AutoCAD command invoked from an AutoLISP program or toolbar macro, you use the backslash \.

    However, since the backslash is an escape character in AutoLISP, you'll need to prefix it with another backslash in order to output a literal backslash to the command, e.g. \\.

    Aside: the pause symbol also evaluates to a backslash, but since this symbol is not protected and may be redefined, I would always recommend using a literal backslash.

    And so we might use:

    (command "line" "\\")
    

    However, this only pauses for a single input, and the AutoCAD LINE command will accept an arbitrary number of inputs, and so we need a way to construct a loop and determine when the user has finished with the command.

    For this, we can make use of the CMDACTIVE system variable, which is a bit-coded system variable indicating the current command state - a bit code of 1 indicates that a command is active.

    To test whether bit code 1 is set, we can use the AutoLISP logand function, which returns the bitwise AND of two supplied integers, e.g.:

    (logand 1 3) => 1
    
    (logand 1 2) => 0
    

    Combining this with the CMDACTIVE system variable we have:

    (logand 1 (getvar 'cmdactive))
    

    Which will return 1 if a command is active, else 0 otherwise.

    Hence the test expression for our loop can be:

    (= 1 (logand 1 (getvar 'cmdactive)))
    

    And now it just remains to construct the loop itself - for this, we can use the AutoLISP while function:

    (while (= 1 (logand 1 (getvar 'cmdactive)))
        (command "\\")
    )
    

    The above will continue to pause for user input whilst a command is active.

    Putting it all Together

    Putting everything together, we have:

    (defun c:testfunction ( / old )
        (setq old (getvar 'clayer))
        (setvar 'clayer "0")
        (command "line")
        (while (= 1 (logand 1 (getvar 'cmdactive)))
            (command "\\")
        )
        (setvar 'clayer old)
    )
    

    However, there are few other enhancements that we can make here...

    Additional Enhancements

    • We can add a (princ) expression to the end of the function definition to suppress the value returned by the last evaluated expression. As written, the current function will return the value returned by the setvar function, which will be the name of the original layer.

      We can use (princ) at the end of the definition to instead return a null symbol to the command line, and hence cleanly exit the program:

      (defun c:testfunction ( / old )
          (setq old (getvar 'clayer))
          (setvar 'clayer "0")
          (command "line")
          (while (= 1 (logand 1 (getvar 'cmdactive)))
              (command "\\")
          )
          (setvar 'clayer old)
          (princ)
      )
      
    • We can prefix the invocation of the AutoCAD LINE command with an underscore to account for localised versions of AutoCAD (in which the LINE command may be called something else):

      (command "_line")
      

      The use of an underscore command prefix ensures that we are invoking the non-localised English LINE command.

    • We can prefix the invocation of the AutoCAD LINE command with a period to account for possible redefined versions of the AutoCAD LINE command:

      (command "_.line")
      

      Since it is possible to redefine AutoCAD commands, the use of a period command prefix ensures that we are invoking the non-redefined standard LINE command.

    • We can add a local error handler to automatically reset the current layer to the original layer if the user presses Esc to exit the program:

      (defun c:testfunction ( / *error* old )
      
          (defun *error* ( msg )
              (if old (setvar 'clayer old))
              (if (not (wcmatch (strcase msg t) "*break,*cancel*,*exit*"))
                  (princ (strcat "\nError: " msg))
              )
              (princ)
          )
      
          (setq old (getvar 'clayer))
          (setvar 'clayer "0")
          (command "_.line")
          (while (= 1 (logand 1 (getvar 'cmdactive)))
              (command "\\")
          )
          (setvar 'clayer old)
          (princ)
      )
      

      For more information on how the local error handler operates, you may wish to refer to my AutoLISP tutorial on Error Handling.

    • We could also test to ensure that layer 0 is not frozen before attempting to set it as the current layer, but this goes beyond the scope of this beginner's program.

    Extending the Idea

    With the above approach we have defined our own custom function to store the current layer, set a new current layer, invoke the LINE command, and then reset the original current layer.

    But this means that the user must remember to invoke our custom version of the LINE command, rather than the standard LINE command.

    What if there was a way to automatically set & reset the current layer when invoking standard AutoCAD commands? Well, through the use of Visual LISP Reactors, there is.

    In my open-source Layer Director application, I demonstrate how you can use Command Reactors & LISP Reactors to automatically create, set, and reset the current layer when standard AutoCAD commands are invoked:

    enter image description here