Tuesday, February 20, 2007

I can already do that

Why are there 4672 different word processors or hundreds of CAD programs available (I made the numbers up)? Because somewhere, sometime, a programmer was sitting at his keyboard whacking away on something he felt was truly important and when someone said "I can already do that", the first words out of the programmers mouth were "yeah, but....". I am today formally proposing that this be adopted as the official motto of petty programmers everywhere. For example there are at least two perfectly good ways to remove the arc codes from a Vr line entity. If you want to clear entire layers of the pesky point flags, delarc is the ticket. For a single line or two edilin has the ever popular B6,B6 combo that will do the same thing for any line you latch on to. "Yeah, but" what if I want clear 5 individual lines in a point and click method where each line identified is cleared of arc codes (or any other flags which is part of the moral of this post). How about clear_arc.py?

As a whole it looks like.

Ws=PyVrWs()
Line=PyVrLine()
WsNum=Ws.Aws()

while Line.Id() != -1:
..Ws.UndoBegin(WsNum,"clear_arc")
..for PointNum in range (0, Line.GetNumXyz()):
....p, a, c, f, m = Line.GetPointFlags (PointNum)
....Line.SetPointFlags (PointNum,p,0,c,f,m)
..Line.ReRec ()
..Line.SetWidth(2)
..Line.Plot()
..Ws.UndoEnd(WsNum)


Taken apart.

Ws=PyVrWs()
Line=PyVrLine()
WsNum=Ws.Aws()
Initialize what will be needed.

while Line.Id() != -1:
Been a while since we've used the Id() function, remember that it returns an open object, ready to interact with.

..Ws.UndoBegin(WsNum,"clear_arc")
This time the undo points are set inside the Id() loop so if the wrong one is identified the user could just exit and undo the last modification while anything else would remain cleared.

..for PointNum in range (0, Line.GetNumXyz()):
For every point on the identified line.

....p, a, c, f, m = Line.GetPointFlags (PointNum)
Get the existing point flags.

....Line.SetPointFlags (PointNum,p,0,c,f,m)
Set them all back as they were except for the arc flag which will be set to 0.

..Line.ReRec ()
Re-record the line that is loaded.

..Line.SetWidth(2)
..Line.Plot()
Change the graphics as a verification and plot it (after the ReRec so graphic changes aren't stored).

..Ws.UndoEnd(WsNum)
Set the undo endpoint for each line identified.

Note that point flags are documented in the VrPython documentation as follows.

p, a, c, f, m Flags

p-Pen code (0=None 1=Up 2=Continue 4=End)
a-Arc flag (0=None 1=Beginning, end or PRC 2=Mid point 3=Point on arc)
c-Code (0-255)
f-Bit encoded flag (0-255)
m-Mosaic width (0-255)

If a parameter is passed as –1 then the original number is not changed

Example Line.SetPointFlags (PntNum, PenCode, ArcFlag, Code, BitFlags, MosaicWidth)

The bit encoded flag isn't used much that I know of and can be a handy way to mark a point for further consideration, Always check with someone at Cardinal Systems before using this flag because if it is implemented for something then there could be conflicts between a python program and Vr functions that use the same bit. A good example of this is to run batnod, id a line that was noded, move to a node point, and look at the f display in the menukeys dialog.

Monday, February 12, 2007

Computing without errors

Wouldn't it be a lot more fun to write code if you could guarantee that those pesky end users would never enter the wrong type of data? Not to mention, shouldn't it be their responsibility to know the bounds of whatever is entered or passed to the program. After all isn't it enough that some helpful soul is writing code to make every one's life easier, why should I care if the "File does not exist". Well we all know that errors happen (sometimes even the program isn't perfect). One helpful tool when unexpected problems arise is the try: - except: block. In simple terms anything that falls within a try: block and causes a potentially fatal error, should cause execution to jump directly to the except: block.

#try-except.py
print '\nTesting try:'

try:
..print "divide 1 by 3 and it's ok",1.0/3.0
except:
..print 'it worked so this never prints'
try:
..print "This prints until it does the division of 1 by 'a' then ",1.0/'a'
except:
..print '\nThere is an error that would normally end the program\nbut instead jumps to the exception'
print '\n'

Should print.
divide 1 by 3 and it's ok 0.333333333333
This prints until it does the division of 1 by 'a' then
There is an error that would normally end the program
but instead jumps to the exception

try-except.py
print '\nTesting try:'

try:
..print "divide 1 by 3 and it's ok",1.0/3.0
Anything that should happen as part of normal execution goes inside the "try" block, probably including a proper exit code if you are a good programmer and this is a non-Vr program.
except:
..print 'it worked so this never prints'
Anything that should be done if there is any kind of error encountered in the try block should go in the "except" block. This is a good place to inform the user that something went wrong. In this example this line never gets printed because there is no error in the code.
try:
..print "This prints until it does the division of 1 by 'a' then ",1.0/'a'
Here the error is that there is a division by a string, notice that if it had worked there would have been some characters printed directly after the word "then", since the error is in the division the error comes at this point and execution would normally print out a bunch of cryptic python like error statements.
except:
..print '\nThere is an error that would normally end the program\nbut instead jumps to the exception'
print '\n'
However since we "trapped the exception", execution continues with the first statement in the except block.

I don't use try for every line of code I write, mostly because I'm lazy and they are for personal or in-house usage. The more likely an important program is to leave the building, the more likely I am to worry about exceptions. Within VrPython I tend to use them where there is a reasonable expectation that the same problem could come up on a regular basis.

Friday, February 09, 2007

News flash for the slow to catch on

While posting on The Scripts Developer's Network python forum, someone pointed out to me that string.atoi (and atof) have been deprecated ( I hope I'm never deprecated, it sounds painful) and the preferred methods are the built-in functions int() and float(). I'm not sure when it happened because I apparently didn't get the memo, but especially if you have moved up to Vr 3.2 and Python 2.5, it is probably something to take note of.

Wednesday, February 07, 2007

It keeps growing, and growing....

Some programs seem a little like Frankenstein's monster. You know, you start out to re-animate a little dead flesh in your spare time, and the next thing you know the peasants are in a uproar. Well, not quite, but it really seems like some of the smallest projects eventually take on a life of their own. (Note that in many places, this would be considered humor, poor humor, but humor. Apologies if it seems offensive where you are)

Here is an example that also shows some interesting concepts. History: One day I was doing something and thought "It sure would be nice if I could throw a north arrow on the screen any time I like to remind me which way north is". Just a simple little thought, easily enough accomplished with a couple of lines of python, nothing stored in the file, just displayed and then erased at the next refresh.. Later I thought, "Sometimes when I'm adjusting a display it would be nice to know where the exact center of the display and it's edges are". Then because we still do the majority of work on analytical instruments, I have always missed the ability in Vr's predecessor to show me where the instrument was in relation to the screen. Well as long as I'm showing North, and the centers of all the edges, why not draw a line from the screen center to the current cursor (instrument) location that would indicate where the instrument is.

See how it goes, next thing you know there are lines and symbols all over the screen, but each one helpful in it's own way. I call this ugly duck screen_center.py (for reasons which now escape me).


def Rad2DecDeg(Radians=0):
..return Radians*(180.00/3.1415926535897931)

Gui = PyVrGui()
Gr=PyVrGr()
Line=PyVrLine()
Sym=PyVrSym()

LineLayer=105
LineGraphic=1
NorthGraphic=58
TickGraphic=41
SymLayer=102
Radius=.75

Gui.DspMsg0('Rotation = %.2f'%(Rad2DecDeg(Gr.GetWinRot()[2])))
Sym.SetCoord(Gr.GetWinOrg()[0],Gr.GetWinOrg()[1],Gr.GetWinOrg()[2])
Sym.SetLayer(SymLayer)
Sym.SetGpoint(NorthGraphic)
Sym.SetRad(Gr.GetWinScale()*Radius)
Sym.SetRot(0)
Sym.Plot()
x,y,z,key = Gr.GetCursor ()
Line.SetLayer (LineLayer)
Line.SetGpoint (LineGraphic)
Line.AddPoint(Gr.GetWinOrg()[0],Gr.GetWinOrg()[1],Gr.GetWinOrg()[2])
Line.AddPoint (x, y, z)
Line.Plot()
Sym.SetGpoint(TickGraphic)
Sym.SetRad(Gr.GetWinScale()*.25)
for corner in range(4):
..x1,y1,z1=Gr.GetWinCorner (corner,0)
..Sym.SetCoord(x1,y1,z1)
..Sym.Plot()
..if corner==3:x2,y2,z2=Gr.GetWinCorner (0,0)
..else:x2,y2,z2=Gr.GetWinCorner (corner+1,0)
..Sym.SetCoord(((x1+x2)/2.0),((y1+y2)/2.0),z1)
..Sym.Plot()

And away we go...

def Rad2DecDeg(Radians=0):
..return Radians*(180.00/3.1415926535897931)
We'll be grabbing some parameters later that will be returned in radians. In the US we (or at least I) visualize decimal degrees more easily. The rotation is just going to be displayed in a message area, so we might as well make it readable. In the actual program I import math, and use math.pi instead of the 3.14 number but I'm trying harder to make these posts independent of python.

Gui = PyVrGui()
Gr=PyVrGr()
Line=PyVrLine()
Sym=PyVrSym()
All this is typical, except this may be the first time that PyVrGr has been mentioned. This is a class that allows us to interact with the graphics window. In this case we'll mostly be getting information from it.

LineLayer=105
LineGraphic=1
NorthGraphic=58
TickGraphic=41
SymLayer=102
Radius=.75
Once it is all said and done there will be a north arrow in the exact center of the display, ticks (simple cross) in the center of the edges, and a line from the screen center to the current cursor position. Radius is in inches and will be set based on the screen scale later. All the symbols will go in the same layer, though there will be a different graphic for the side ticks, and the center north arrow.

Gui.DspMsg0('Rotation = %.2f'%(Rad2DecDeg(Gr.GetWinRot()[2])))
If we end it here, there will just be a message in the Vr message area that give us the screen rotation, helpful in it's own right, but let's add a graphic display.

Sym.SetCoord(Gr.GetWinOrg()[0],Gr.GetWinOrg()[1],Gr.GetWinOrg()[2])
Here is a python concept, GetWinOrg returns a 3 number list with x,y, and z as the values. In the above line SetCoord will set the symbols x,y,z location values with the list values returned by GetWinOrg. If the list returned would be (1,2,3) then list member [0] (the first member) would be 1, member [1] (second) would be 2 and so on. So Gr.GetWinOrg()[0], means the first member of a list returned by the function GetWinOrg() which is a part of the PyVrGr class of which Gr is an instance.

Sym.SetLayer(SymLayer)
Sym.SetGpoint(NorthGraphic)
Sym.SetRad(Gr.GetWinScale()*Radius)
Sym.SetRot(0)
Sym.Plot()
Just set the center symbols visual display based on the attributes decided on above and plot it, again; since GetWinScale returns the scale in units per inch (or mm) we will multiply it out so the display will be set in screen inches.

x,y,z,key = Gr.GetCursor ()
Get the current position of the cursor. This function will really come in handy later, but for now we just want a one time reading of where the current input device is located whether it is a stereo instrument or the mouse.

Line.SetLayer (LineLayer)
Line.SetGpoint (LineGraphic)
Line.AddPoint(Gr.GetWinOrg()[0],Gr.GetWinOrg()[1],Gr.GetWinOrg()[2])
Start by setting the display properties then using the method illustrated above, set the xyz to the window center.

Line.AddPoint (x, y, z)
Line.Plot()
Then add the second point based on the cursor location grabbed earlier, and plot the line.

Sym.SetGpoint(TickGraphic)
Sym.SetRad(Gr.GetWinScale()*.25)
Change the symbol attributes to those used for the corner tics.

for corner in range(4):
There are four corners, numbered 0-3 and the range function will just create a list starting at 0 and ending at the number before whatever is entered. In this case it would return (0,1,2,3). range() actually has a lot more flexibility including setting start point, end point, and increment, but for now we just need a list from 0-3.

..x1,y1,z1=Gr.GetWinCorner (corner,0)
GetWinCorner needs a corner number and a window number, in case there are multiple graphics windows open.
Corner numbers are LL=0, LR=1,UR=2, UL=3

..Sym.SetCoord(x1,y1,z1)
..Sym.Plot()
Plot a symbol in whichever corner is currently up in the list.

..if corner==3:x2,y2,z2=Gr.GetWinCorner (0,0)
There will also be tics at the midpoints between corners, to do this we'll get the average between current corner and next, but if current corner is 3 (UL) then next would be 0 (LL).

..else:x2,y2,z2=Gr.GetWinCorner (corner+1,0)
If the current isn't 3 then get the values from the next corner.
..Sym.SetCoord(((x1+x2)/2.0),((y1+y2)/2.0),z1)
set a symbol at the average between current and next.
..Sym.Plot()
and plot it.

Here are the end results.

For anyone interested in trying VrPython for the first time or if you are early in the game, I suggest going to the earliest posts and working forward. I use VrPython every day for many wonderful things, needless to say it will change and could potentially damage a file. Any risk associated with using VrPython or any code or scripts mentioned here lies solely with the end user.

The "Personal VrPython page" in the link section will contain many code examples and an organized table of contents to this blog in a fairly un-attractive (for now) form.