First time asker, so hopefully I'm describing the problem well enough.
We have an Autolisp code in our company that is used by several individuals with the same version of AutoCAD, but for some of the users the lisp has stopped functioning correctly.
The function of the lisp is as follows:
This should lead to a block with a marker and a text with the following format (prefix)(possible middle section if the number doesn't consist of three numbers)(the running number), e.g. PT001 or PX100.
Instead of doing this however some of the users have been experiencing the lack of prefix and number and started seeing only the aforementioned possible middle section of the text, while other times the same user can experience that only the prefix is shown. The marker is displayed as it should be, but the text just won't work as expected.
Any help in analyzing the code below for flaws is greatly appreciated.
If the code seems to be "flawless", I assume that there is a problem with the block or its attributes.
-E
(defun c:pointnumber()
(setvar "ATTDIA" 0)
(setq sc (getreal "\nEnter scale: "))
(setq px (getstring "\nSet prefix for point number: "))
(setq nr (getint "\nThe number for the first point: "))
(setq ic (getint "\nIncrement of the number: "))
(setq point 1)
(while (/= point nil)
(setq point (getpoint "\nChoose a point: "))
(if (/= point nil)
(progn
(setq inr (itoa nr))
(if (< nr 100) (setq md "0"))
(if (< nr 10) (setq md "00"))
(if (> nr 99) (setq md ""))
(setq ph (strcat px md inr))
(command "insert" "pointnumber" point sc sc 0 ph)
(setq nr (+ nr ic))
)
)
)
(setvar "ATTDIA" 1)(princ)
)
There are a number of issues with your current code: some of which might merely be considered bad practice, some will cause the program to fail if the user responds with invalid data, and others will cause the program to fail or behave unexpectedly depending on the settings of the AutoCAD environment in which the program is executed.
The main culprit for the behaviour you have described is likely to be the ATTREQ
system variable, which determines whether the user would receive prompts for attribute values as part of the INSERT
command. If ATTREQ=0
when the program is run, the block would be inserted with its default attribute values.
You can ensure consistent behaviour between environments by storing the current value of this system variable and setting it to 1
prior to calling the INSERT
command (to ensure that attribute prompts are issued), and then restoring the original value following the command or at the end of the program.
For example:
(defun c:test ( / atr )
(setq atr (getvar 'attreq))
(setvar 'attreq 1)
;; ... Do your thing
(setvar 'attreq atr)
(princ)
)
When supplying point data to a command through AutoLISP, the point will be affected by any Object Snap modes active at the time that the point is supplied. I describe this in more detail in my answer here.
The easiest way to avoid this is through the use of the "_non"
or "_none"
object snap modifier to instruct AutoCAD to ignore all Object Snap modes for the subsequent point input, e.g.:
(command "insert" "pointnumber" "_non" point sc sc 0 ph)
You should account for a lack of user input or invalid user input to avoid errors during program execution - this is easily achieved either through the use of if
statements or the initget
function, e.g.:
(initget 7) ;; Prevents Enter, zero, or negative numbers
(setq sc (getreal "\nEnter scale: "))
Or:
(initget 6)
(if
(and
(setq sc (getreal "\nEnter scale: "))
(setq px (getstring "\nSet prefix for point number: "))
(setq nr (getint "\nThe number for the first point: "))
(setq ic (getint "\nIncrement of the number: "))
)
;; ... Do your thing
)
Alternatively, you can configure default values for each of these prompts, using one of the methods I describe in my tutorial on Prompting with a Default Option, for example:
(setq sc (cond ((getreal "\nSpecify scale <1.0>: ")) (1.0)))
Currently, all of the variables in your program are global variables: that is, they are defined within the document (drawing) namespace and will retain their values even after the program has completed its execution.
As I describe in my tutorial on Localising Variables, this can potentially cause problems if such variables inadvertently share their names with global variables used by other programs, or when a program is constructing a list or other acculumative data structure within a loop.
Unless the use of a global variable is necessary for the correct operation of the program, I would suggest declaring those variables local to the function, e.g.:
(defun c:pointnumber ( / ic inr md nr ph point px sc ) ;; Local variables
;; ...
)
Supplying the block name directly to the INSERT
command assumes that either a definition of that block already exists within the active drawing, or that a drawing with that filename exists either within the working directory or an AutoCAD Support File Search Path - if neither condition is met, the INSERT
command will error during program execution.
You can therefore test these conditions beforehand, notifying the user if the block is not found, else proceeding to execute the remainder of the operations:
(if
(or
(tblsearch "block" "pointnumber") ;; Checks for existing definition
(findfile "pointnumber.dwg") ;; Checks for drawing file
)
;; ...
)
You can also use the cond
function in place of a sequence of if/else
expressions.
Since you're changing system variable values during program execution, you should ensure that the user's AutoCAD environment is reset to its original state in the event of an error during program execution - noting that the user pressing Esc to exit the program will also result in an error.
You can accomplish this by defining a local error handler, as I describe in my tutorial on Error Handling. The local error function is evaluated if an error is encountered during program execution, and so you can include expressions within the definition of this function to reset the AutoCAD environment to its original state - in your case, this would involve resetting the original value of the ATTDIA
system variable.
;; Define function, declare local variables
(defun c:pointnumber ( / *error* bn ic nr ns pt px sc vl vr )
;; Define local error function to reset system variables on error
(defun *error* ( msg )
(mapcar 'setvar vr vl) ;; Reset list of system variables
(if (not (wcmatch (strcase msg t) "*break,*cancel*,*exit*"))
(princ (strcat "\nError: " msg))
) ;; end if
(princ)
) ;; end defun
;; Define block name
(setq bn "pointnumber")
(if (or (tblsearch "block" bn) ;; Definition in drawing
(findfile (strcat bn ".dwg")) ;; Drawing file in support path
) ;; end or
(progn
(initget 6) ;; Prevents 0 & negatives
(setq sc (cond ((getreal "\nSpecify scale <1.0>: ")) (1.0))
px (getstring "\nSpecify prefix <none>: ")
) ;; end setq
(initget 4) ;; Prevents negatives
(setq nr (cond ((getint "\nSpecify starting number <1>: ")) (1)))
(initget 6) ;; Prevents 0 & negatives
(setq ic (cond ((getint "\nSpecify increment <1>: ")) (1)))
(setq vr '(attreq attdia cmdecho) ;; List of system variables
vl (mapcar 'getvar vr) ;; Store current values
) ;; end setq
(mapcar 'setvar vr '(1 0 0)) ;; Set system variables appropriately
(while (setq pt (getpoint "\nSpecify point <exit>: "))
(setq ns (itoa nr)
nr (+ nr ic)
)
(repeat (- 3 (strlen ns)) (setq ns (strcat "0" ns))) ;; Pad to 3 digits
(command "_.-insert" bn "_S" sc "_R" "0" "_non" pt (strcat px ns))
) ;; end while
(mapcar 'setvar vr vl) ;; Reset list of system variables to their original values
) ;; end progn
;; Else the block was not defined/found
(princ (strcat "\nThe block \"" bn "\" is not defined in the active drawing and cannot be found."))
) ;; end if
(princ) ;; Suppress the value returned by the last evaluated expression
) ;; end defun
There are other possible improvements which could also be implemented, such as:
Removing reliance on calls to standard AutoCAD commands (in this case the INSERT
command) through the use of the ActiveX insertblock
method or the entmake
/entmakex
functions to write DXF data directly to the drawing database.
Populating attributes by referencing their attribute tag names so as to remove dependence on the order in which the attribute references are encountered within the block reference (which could be modified on a per-drawing basis through the use of the BATTMAN
command).
Using a 'dynamic default' (as described in my tutorial), and potentially storing the value of the defaults between drawing sessions.