Wednesday, December 17, 2008

More Macro Intermediates

Ok, so technically, I hit the "Send to Weblog" button yesterday when I meant to hit "Save as Draft". But, that post was long enough already anyway, so I'm just rolling with it. I have rearranged my icons so that shouldn't happen again, and pressing forward.

Error Apparent

Error handling is an important aspect of any programming effort, macro or otherwise. In the case of full-bore applications, improperly handled errors or conditions can result in all kinds of nasty behavior, even crashes. In the case of macros, maybe the user could run the program at the wrong time, or against the wrong set of assumptions. It is worth it to invest some time to keep users from stepping on their own toes.

Let's start with a simple of example of when a user should not be able to run a program. I-DEAS knows when a read-only drawing is on the screen, and it knows that since it is read-only, you shouldn't be able to make changes like drawing a new line or circle, or adding a dimension. I-DEAS enforces this by disabling any of the commands that would allow you to create new content. But it is selective in icon disabling; creating a new drawing can happen at any time, so that icon remains available all the time.

Unfortunately, I-DEAS does not provide any means for disabling custom icons. This means that macros that add, change, or delete stuff are always able to be run. They won't succeed; the drawing is still read-only after all, but you should still attempt to handle that gracefully rather than shove a bunch of obscure error messages down a user's throat. Let's take a look at the solution I came up with to handle this situation:

C: --- Determine if the drawing has write permissions. If it
C: --- does (1), no further steps are necessary. If it dooesn't (0),
C: --- Keep going to check what the permissions are.
# Inquire Drawing WritePriv (WritePriv)

C: --- The Inquire Drawing Library command does not work if the drawing has
C: --- been displayed via View Drawing. In this case, the output variable does
C: --- not change. Define an initial value to allow for detection of this case.
# LibStatus = "View Drawing (Read Only)"
# Inquire Drawing Library ( LibStatus )

Don't worry too much about the specific mechanics. Basically, I ask the drawing if write permissions are available. That's a yes/no question effectively, but there are some I-DEAS nuances to handle, which requires me to ask the question again a different way. This way I can be confident that regardless of the means that the user is looking at a drawing, I'm correctly and consistently determining the permissions available. Once I have that information, I need to act on it:

# IF (WritePriv EQ 0) THEN problem = 1
# IF (problem EQ 1) THEN Show Message ("*** ERROR: Drawing is not checked out.")
# IF (problem EQ 1) THEN Show Message ("*** The library status of this drawing is: " + LibStatus)
# IF (problem EQ 1) THEN Show Message ("*** If must be CK, CO, or new in order to add a sheet")
# IF (problem EQ 1) THEN Show Message ("*** Change Library Status and then try again.")
# IF (problem EQ 1) THEN Show Message ("*** Macro is quitting without changing anything.")
# IF (problem EQ 1) THEN GOTO EndOfAddAnotherDPage

Given the answer to the question "What are the permissions?", I then determine if the permissions are what I want. If they are not, I display information to the user informing them of this fact, tell them what they need to do about, and then skip to the end of the program without actually doing anything to the drawing. This is much more human-friendly than any of the error messages the user likely would have received if the program had continued to run.

This is the good category of error handling. You define what is allowed, and what isn't, then specifically address as many cases of the not-allowed kind as you reasonably can. The bad category is macro language failures and/or limitations, or bugs in the system. Those are hard core errors, and generally speaking there isn't much you can do about it, at least in I-DEAS. The macro will crap out in a not-elegant way, and you and your users will be left scratching your heads about what went wrong. You will have to figure out a way to work around these cases.

I said that the time spent on error handling is worth it, but you do reach a point of diminishing returns. You just aren't going to think of every possible thing that could go wrong, and usually slapping user's hands is more fun anyway.
"Brian, the macro didn't work."
"Ok, let's take a look at what you did... It seems to be working now."
"Oh, I may have been holding down the Z key when it ran last time."
"Why did you do that?"
"I don't know."
"Stoppit." *whack*

Follow the Path

Debugging is a part of programming life. It's necessary to get the program working in the first place, and will still be necessary when the program fails later. From what I've seen, the Visual Basic editor (SolidWorks, Excel, etc.) provides some powerful tools for stepping through a program line by line in order to figure out where something went wrong. I-DEAS does not. So, you have to improvise. My solution (and I'm certain I didn't invent it, but I don't remember where I picked it up) is to use a tracer variable to help display what is going on at different points in the program. Pretty much all of my programs will begin with something like this:

C: --- FLAGS
C: --- Set trace to 1 to see all messages
# trace = 0
# problem = 0

I use the problem variable for my error handling, as seen in the previous example. Trace is how I gain insight into what the program is doing. I'll use this in a number of different ways. First, I can use it to see when I've performed a particular command:

C: --- Select everything in the main view
# view select_all(NumEnts)
# IF (trace EQ 1) THEN Show Message ("Selected all entities in view")

In this case, if I have the tracer turned on, I will see a message informing me that all entities have been selected. So if I personally observe that nothing was selected, I know I have a problem. Once I convince myself that the program is working, I'll turn the tracer off, and the user will not be any wiser about these messages buried in the program. If new problems arise, I turn the tracer back on and track down what happened.

I can use it anytime a variable's value has been assigned, calculated, changed, or for any other reason I just want to know what it is:

# ideal_x = Xclick - HalfNomWidth
# ideal_y = Yclick - HalfNomHeight
# IF (trace EQ 1) THEN Show Message ("ideal_x is: " + ideal_x)
# IF (trace EQ 1) THEN Show Message ("ideal_y is: " + ideal_y)

I can use a hand calculation to know what the value should be, and if the value is different, I know there is a problem in the code. I can use it to determine when I jump around inside the program:

# SUBROUTINE getuserclick
C: --- Display pickpoints and get user selection
# IF (trace EQ 1) THEN Show Message ("Entered getuserclick subroutine")
# IF (trace EQ 1) THEN Show Message ("Leaving getuserclick subroutine")

If the subroutine never gets called, I won't see the message, and know that there is a problem with the flow control elsewhere in the program. This is also useful for loops:

C: --- Repeat loop for each possible sheet
# page_counter = 1
# WHILE (page_counter LE total_sheets) DO
# IF (trace EQ 1) THEN Show Message ("Start of page_counter loop, pass #: " + page_counter)
# IF (trace EQ 1) THEN Show Message ("End of page_counter loop, pass #: " + page_counter)
# page_counter = page_counter + 1

If you know that the loop needs to run 5 times, and it either quits too soon or runs too long, you can use this to figure out exactly when something went wrong.

In my case, when I turn the tracer on, a whole host of these messages gets dumped into the list window. Then I can read through what is effectively a script of what happened and determine where things went wrong, and hopefully figure out why. Turn the tracer back off when things are fixed, and the users just see their normal macro behavior.

You do sacrifice some performance this way. Each of the message statements involves a conditional element that the software has to spend time evaluating, even if you are not ultimately generating the message. Still, I find the information produced to be invaluable, especially when I'm revisiting one of my more complicated programs that I haven't touched for months because a user found a new problem.

Hopefully these posts will prove useful in getting started along your macro creating career. In the future, I'll walk through a fully developed macro, and explain how it works, and the thought process that lead to its creation.

1 comment:

Anonymous said...

Good Blogs
What is hard for me for doing macros is the format? How and when to use () spaces and such. Very nice