AutoLISP Error catching...

AutoLISP Error catching...

Anonymous
Not applicable
5,649 Views
12 Replies
Message 1 of 13

AutoLISP Error catching...

Anonymous
Not applicable

After a VERY long break from AutoLISP programming, I find myself pulled back in.

I've written a program to enhance the revcloud command based on how our users would like the command to work.

Anyway, it's almost perfect except for my error catching routine which, as you know, returns the user's settings back to the way they like them and exits gracefully if something goes wrong.

I've created a while loop in the middle of the pline command and the user can continue to pick points until they hit Enter which dumps them out and completes the pline command.

I've been testing the possibility of the user hitting ESC during this point picking exersices and it seems that my error catching routine is not being called.

The strange thing is it works beautifully the first time the LISP rountine is used. The next time it's used, is when the error flys by my error catching routine.

I've noticed that the error message is:  ; error: Function cancelled  and this seems to get ignored by *error* which has been redefined by my get_error routine.

The fact that it works the first time leads me to believe that a system variable or something is getting changed after my enhanced revcloud routine is run for the first time.

I've noticed that there are some new error functions but the explanations of these and how they are used are a little vague. I tried using vl-catch-all-apply but that didn't seem to get me anywhere.

So, I guess my question is how do I catch  ; error: Function cancelled  or any and ALL errors that might get thrown in my AutoLISP routine?

We are running AutoCAD Architecture 2014 and will be upgrading to 2016 in the next few months.

 

I would greatly appreciate any and all help anyone could give me.

 

Thank you,

Matt

0 Likes
Accepted solutions (1)
5,650 Views
12 Replies
Replies (12)
Message 2 of 13

hmsilva
Mentor
Mentor

Hi Matt,

 

currently I use a generic *error* function, to catch and restore.

 

This is a simple *error* sample, which should catch that error

This Lee Mac's Error Handling tutorial, may be of interest.

 

Hope this helps,
Henrique

EESignature

0 Likes
Message 3 of 13

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

....

I've been testing the possibility of the user hitting ESC ... and it seems that my error catching routine is not being called.

.... it works beautifully the first time the LISP rountine is used. The next time it's used, is when the error flys by my error catching routine.

.... the error message is:  ; error: Function cancelled  and this seems to get ignored by *error* which has been redefined by my get_error routine.

The fact that it works the first time leads me to believe that a system variable or something is getting changed after my enhanced revcloud routine is run for the first time.

.... my question is how do I catch  ; error: Function cancelled  or any and ALL errors that might get thrown in my AutoLISP routine?

....


Whether or not it suppresses the error message is a separate question from whether or not it resets System Variables.  But since it looks like it's giving you AutoCAD's standard error message for hitting Esc when in an AutoLisp function, I would concur that probably your handler is not being invoked, and that would explain the System Variables not being reset.  If it's not being invoked, it's probably not being (defun)'d properly, or if you don't have it as a localized function, maybe it's not being installed in place of AutoCAD's properly.

 

If you post the code [even just the (defun) line with its localized variables list, the *error* part, the part that replaces AutoCAD's with that if it's not a localized function, and the parts that change System Variables and change them back], maybe someone can see where the problem lies.

Kent Cooper, AIA
0 Likes
Message 4 of 13

Anonymous
Not applicable

Hi Kent,

 

I appreciate your comments and suggestions. However, I think you may have misinterpreted what I meant about a sysvar getting changed.

Yes, my routin does set/change some sysvars and my error routine (when working) returns them to their original setting when exiting gracefully.

I'm referring to a sysvar that gets changed (without my knowing) when running my routine. For example, like the drawing changed sysvar because a polyline is being added to the drawing. For this routine, I don't review or change that. Or, as another example, the ERRNO sysvar. Again, I'm not monitoring or adjusting that but it can get changed based on what happens in my routine. Either way, here is a synopsis of what I have.

Within the definced command function, I call a localized function to get/set/store some sysvars and redefine the error handler which is locally defined as well. That function calls a localized undo function which sets a mark point so I can roll back in the event of an cancelation or error. Then the "regular" routine runs. If all goes well, the last localized function runs to reset any sysvars that I changed during the routine. Here are the locally defined functions I mentioned above.

The loading routine gets called first.

;;;*******************************************
;;;-***  Loading Program Routine  ***-
;;;*******************************************
(defun get_load ()
;;; (alert "Gettys load function called! ") Used for testing. As you can see, I commented this out.
(get_u-chk) This calls the undo marking routine, see below...
(setq sysvars
 (mapcar '(lambda (is now) (setq var (getvar is)) (setvar is now) (list is var))
  '("attdia" "attreq" "cmdecho" "expert" "orthomode" "osmode" "regenmode" "polarmode")
  '(0 0 0 5 0 35 0 6)
 )
)
(setq exlyr (getvar "clayer"))
(setq old_error *error* *error* get_error) Here's where I redefine the error catching routine, see below...
(setq err (findfile "z:/CAD Library/Blocks/2d/Anno_Rev_Tag.dwg"))
 (if (equal err nil)
  (progn
   (alert "Could not locate Revision Cloud tag.
    \nCreate a support ticket (support@gettys.com)
    \nto inform the CAD Manager.")
   (get_unload) This calls the unloading or exiting routine if the revision tag block is not found, see below...
  )
 )
)
;;; ******************************************
;;; ***** Error Handling Routine *****
;;; ******************************************
(defun get_error (s)
(alert (strcat "Gettys Error function called! \nThe error is: " s)) This is used for testing and this is how I know that this error routine is not being called on the second run of the program. This alert never comes up on the second run but does on the first run of the program.

As I said, the error message displayed on the second run is:  ; error: Function canceled  but this sidesteps this error function for some reason.
(if (member s '("Function Cancelled" "Function cancelled" "console break" "quit / exit abort"))
 (progn
 (alert (strcat "Error: " s)) The next 2 lines are, again, for testing. Originally a prompt, but I wanted to see if it was making it to this point so I changed it to an alert.
 ;;(prompt (strcat "Error: " s))
 (alert (strcat "\nThe CMDACTIVE sysvar is: " (itoa (getvar "cmdactive"))))
 (if (not (equal (getvar "cmdactive") 0))
  (command)
 )
 (command "_.undo" "end")
 (command "_.undo" "")
 (get_unload)
 )
)
)
(defun abort (msg)
(if msg (alert (strcat "Application Error: imp_getrc.lsp\n\n" msg "\n")))
(get_unload)
)
;;; ********************************
;;; ***** Gettys Undo Routine *****
;;; ********************************
(defun get_u-chk (/ cmde)
(setq cmde (getvar "cmdecho"))
(setvar "cmdecho" 0)
(command "_.undo" "_end")
(command "_.undo" "_begin")
(setvar "cmdecho" cmde)
)
;;; **************************************
;;; ***** Gettys Un-Loading Routine *****
;;; **************************************
(defun get_unload (/ cmde)
(alert "Gettys Unload function called! ")
(setq cmde (getvar "cmdecho"))
(setvar "cmdecho" 0)
(command "_.undo" "_c" "_all")
(if sysvars
 (foreach var sysvars
  (apply 'setvar var)
 )
)
(setvar "clayer" exlyr)
(if old_error
  (setq *error* old_error get_error nil old_error nil)
)
(setq sysvars nil
  err nil
 ldr nil)
(setvar "cmdecho" cmde)
(princ)
)

 

Does anything look wacky to you?

 

Mb

0 Likes
Message 5 of 13

Anonymous
Not applicable

Thanks for the reply and links.

I will check them out to see if I can gleam any insight from them.

 

I replied to Kent's post and provided my general purpose error rountine. Take a gander at it and let me know if I'm missing something in mine.

 

Mb

0 Likes
Message 6 of 13

hmsilva
Mentor
Mentor

try to change

(member s '("Function Cancelled" "Function cancelled" "console break" "quit / exit abort"))

 to

(wcmatch (strcase s) "*CANCEL*,*BREAK*,*QUIT*,*ABORT*")

 

Henrique

EESignature

0 Likes
Message 7 of 13

Anonymous
Not applicable

Yeah, I thought of that too, but since my alert didn't pop-up, I assumed that it wasn't even getting that far.

Notice, I have an alert statement as the very first line in my error function. That should pop-up when that function gets called. It doesn't. So using wcmatch is of no consequence.

 

Thoughts?

0 Likes
Message 8 of 13

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

.... here is a synopsis of what I have.

.... 

Does anything look wacky to you?

....


I haven't noticed anything wacky [yet], but if you're headed for the 2016 version, one thing you will need to do is eliminate (command) functions from anything invoked by the *error* handler, such as those doing the Undo End bit.  [You can actually keep them if you jump through certain hoops, but it's probably better to change them to VLA methods.  You could use (command-s) functions instead, but those won't be recognized by earlier versions, whereas the VLA approach will.]

Kent Cooper, AIA
0 Likes
Message 9 of 13

Kent1Cooper
Consultant
Consultant
Accepted solution

@Anonymous wrote:

.... here is a synopsis of what I have.

....

;;;*******************************************
;;;-***  Loading Program Routine  ***-
;;;*******************************************
(defun get_load ()
  ....

  (setq old_error *error* *error* get_error) Here's where I redefine the error catching routine, see below...
....

)
;;; ******************************************
;;; ***** Error Handling Routine *****
;;; ******************************************
(defun get_error (s)
  ....

  (get_unload)
  ....

)

....

 

;;; **************************************
;;; ***** Gettys Un-Loading Routine *****
;;; **************************************
(defun get_unload (/ cmde)
  ....

  (if old_error
    (setq *error* old_error get_error nil old_error nil)
  )
....

 

Does anything look wacky to you?

....


A possibility:

 

Note that the get_error function invokes the get_unload function, and that the get_unload function sets the get_error definition to nil.  That means that unless the whole batch of code is APPLOADed again first, there will be no definition of get_error the next time the command that calls for these things is run.  The get_load function will define *error* as nil, which I believe is what would make it revert to AutoCAD's base definition.

 

For a start, try at least removing the setq-ing of get_error to nil from the get_unload function, and at least see whether it then invokes your *error* handler on subsequent use.

 

There are other things I could comment on, but I hope that will at least fix your current problem....

Kent Cooper, AIA
Message 10 of 13

Anonymous
Not applicable

Hmmm... I see what you're saying in theory and that could possibly by why it works the first time and not the next. Interesting.

I used this error rountine long ago in some R14 programs and NEVER had a problem.

 

FYI, I'm calling the get_unload function in the error function but only if certain conditions are true. However, it's worth a shot.

I'll test and post my results.

 

Regarding your other comment, please share. I realize that I might have some "getting up to speed" issues with my programming.

For starters, I probably should be using the VLisp interface instead of notepad but old habits die hard I guess.

 

Thanks for the insight.

Mb

0 Likes
Message 11 of 13

Anonymous
Not applicable

Well I'll be dipped in poop!

 

That worked Kent. It works as it should now.

 

Thanks for the kean logic eye there.

 

Mb

0 Likes
Message 12 of 13

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

.... 

Regarding your other comment, please share. I realize that I might have some "getting up to speed" issues with my programming.

....


The main thing I noticed is that you're using an "old-fashioned" way of doing error handling.  The use of a variable such as old_error was the only way to do it long ago, but for quite a while [I think since R14?], it's been possible to declare *error* as a localized function, just like a localized variable.  Then you can (defun) *error* for use only within the routine, and that definition of it will disappear when the routine finishes, and it will revert back to AutoCAD's error handler.  You can do this:

 

(defun C:WhateverCommandName (/ *error* var1 var2 var3); declare it as localized in the command definition(s)

  (defun *error* (s) (get_error)); define it by reference, inside any command that uses the same one [you can have different ones for different sets of related commands]

  (get_load)

  ....  etc.  ....

); defun

 

In the definition of get_error, remove the argument [the one coming from *error* above will feed any error message in]:

 

(defun get_error ()

  ....  etc.  ....

 

And then, you can entirely do away with the old_error variable and both this:

 

(setq old_error *error* *error* get_error)

 

and this:

 

(if old_error
  (setq *error* old_error get_error nil old_error nil)
)

 

 

There's a more concise way of saving and changing and re-setting multiple System Variables that I've taken to using most of the time.  You put the names in a list, get their current values in a list, set them to the values you want in a list, and re-set them from the initial-values list.  You can replace this part:

 

(setq sysvars
 (mapcar '(lambda (is now) (setq var (getvar is)) (setvar is now) (list is var))
  '("attdia" "attreq" "cmdecho" "expert" "orthomode" "osmode" "regenmode" "polarmode")
  '(0 0 0 5 0 35 0 6)
 )
)

 

with this:

 

(setq

  svn '(attdia attreq cmdecho expert orthomode osmode regenmode polarmode); = System Variable Names

  svv (mapcar 'getvar svnames); = System Variable Values [initial/current]

)

(mapcar 'setvar svn '(0 0 0 5 0 35 0 6)); give them the values you want

 

Not a heck of a lot shorter, but some, and it makes more of a difference to the re-setting part.  You can replace this part:

 

(if sysvars
 (foreach var sysvars
  (apply 'setvar var)
 )
)

 

with this:

 

(mapcar 'setvar svn svv); reset values

 

Since get_load is run first, and those svn and svv variables are set before there's any opportunity for the User to hit Escape or anything, there's no need to check whether they're there to decide whether to re-set them -- they will be.

 

 

If you localize your variables, they'll go away when the routine or function is finished, and you won't need anything like this:

 

(setq sysvars nil
  err nil
 ldr nil)

 

 

Rather than check whether a variable is equal to nil, you can just check whether it doesn't exist at all.  You can replace this:

 

(if (equal err nil)

 

with this:

 

(if (not err)

 

But in that particular case, you also just do this, eliminating the err variable entirely -- replace:

 

(setq err (findfile "z:/CAD Library/Blocks/2d/Anno_Rev_Tag.dwg"))
 (if (equal err nil)
  (progn

 

with just:

 

(if (not (findfile "z:/CAD Library/Blocks/2d/Anno_Rev_Tag.dwg"))
  (progn

    ....

Kent Cooper, AIA
0 Likes
Message 13 of 13

Anonymous
Not applicable

Kent,

 

Thanks for the feedback.

I see what you're saying and like what you're suggesting.

I'll revise my code and take these suggestions into account.

 

Actually, I caught your last suggestion about the err variable and removed it before you replied, putting the (if (findfile... in place rather than testing the variable.

 

I always try to keep my variables localized but as you said, the "old fashioned" way was to have some varibles global so the localized functions and other non-local functions could access them. Long ago, many of the LISP routines I wrote would rely on some global variables in order to perform certain actions based on the drawing type or depending on which block was being inserted.

 

Anyway, I could go on and on explainin all my reasons but I get what you're saying and as we know, the more variables used, the more RAM used.

 

I really do appreciate your comments. I don't know of any other AutoLISP programmers personally, so I never have anyone to bounce idea's off of or help me when I'm stuck.

 

Thanks again,

Mb

0 Likes