Monday, January 29, 2007

Rounding line coordinates

Things have been a little sparse here, but on the plus side I have made considerable progress on my BeginnersVrPython script. While discussing it with a co-worker it seemed much more productive to just give him some very simple examples to show the absolute basics, digging into the documentation, playing with different functions, and learning python will follow naturally with curiosity. Vr actually comes with lots of great example scripts that would give a newbie everything necessary, I just decided to do this as a way of organizing things I would go over with someone new to VrPython as if I were walking them through it.

Anyway, let's look at another example of looping over all the lines in a file, but we'll narrow it to a specific layer. Sometime later remind me to post my Vr number line parser, but for now we'll work with single layers. The task was that it was necessary to take every line in a particular layer and round the coordinates to the nearest even foot. To accomplish this we'll create a function that will do the rounding when passed a number and rounding factor. For example instead of the nearest foot, the user may want to round to the nearest 100 feet or something similar, so even though the math is easy enough to put in the working code, it will be more flexible if we define a function. This will involve two loops, one to check every line in the file to see if it matches the layer of interest, then if so to manipulate every vertex on the line.


def roundto(NumberToRound,By):
..return (round(NumberToRound/By))*By

import string

Ws=PyVrWs()
Line=PyVrLine()
Gui=PyVrGui()

WsNum=Ws.Aws()
Ws.UndoBegin(WsNum,"round_xy")
input=Gui.InputDialog('Set Layer','Enter layer for box <105>')
if input[1]=='':
..layer=105
else: layer=string.atoi(input[1])

for EntNum in range (Ws.GetLineCount(WsNum)):
..if layer==(Ws.GetLineLayer(WsNum,EntNum)):
....Line.Load (WsNum, EntNum)
....for PntNum in range (WsNum, Line.GetNumXyz()):
......(x, y, z) = Line.GetPoint (PntNum)
......(p, a, c, f, m) = Line.GetPointFlags (PntNum)
......Line.ChgPoint (PntNum, roundto(x,1.0), roundto(y,1.0), z,p, a, c, f, m)
....Line.ReRec()
....Line.Plot()

Ws.UndoEnd(WsNum)

Line by line it looks like this.

def roundto(NumberToRound,By):
..return (round(NumberToRound/By))*By
Straight python here, defining a new function called roundto. It takes a number and a factor for rounding and returns the original number rounded by the factor. Tried and true formula but if it's new for some reason here are some examples.
roundto(106.6,.5) = 106.5
roundto(106.6,1.0) = 107
roundto(106.6,5.0) = 105
roundto(106.6,10.0) = 110
roundto(106.6,25.0) = 100
and so on

import string
There are other more complex ways of doing what I want later, but for this example I really wanted it short and simple which meant using the atoi() function to convert a numeric string to an integer. A good example of why it is helpful to have python installed.

Ws=PyVrWs()
Line=PyVrLine()
Gui=PyVrGui()
Initialize all the Vr objects we will be using.

WsNum=Ws.Aws()
When loading or saving entities it is necessary to specify a workspace number. Typically it would probably be the currently active workspace.

Ws.UndoBegin(WsNum,"round_xy")
Setting an undo point is always a good idea.

input=Gui.InputDialog('Set Layer','Enter layer for box <105>')
if input[1]=='':
..layer=105
else: layer=string.atoi(input[1])
The PromBox functions are actually a much better way of doing this, but it's an old script and I'll throw in those functions at a later date. The if statement just sets the default layer if none is entered, and the else sets the layer if one was. The main problem here is that there is no error checking. If the user enters an alpha character it will just crash, this could be fixed with a try/except test (a very worthwhile subject to look up or discuss later). The other option is just to use PromBox functions, which is what they were designed for.

for EntNum in range (Ws.GetLineCount(WsNum)):
This is only the second time it's been used so let's talk about the for loop. In my mind one of the most powerful features of python is how it handles sequence data, I may discuss it later but the concept of a "list" is, in my mind, one of the coolest parts of python. If I had a list called Alist which contained data [1,2,3,4] and said
for data in Alist:
..print data
I would get the return
1
2
3
4
In essence the for loop is going to work over a sequence of data and process each member, whether it is a list, a dictionary, or a string. If you don't have a list, but just want to run a loop a certain number of times the range() function can build a list of numbers which can be processed. In this case we want to process every entity number from the first to the last up to the limit of the linecount.

..if layer==(Ws.GetLineLayer(WsNum,EntNum)):
However we already determined that only the lines which match the desired layer will be processed. The Ws version of getting the layer is used because it is much faster and it won't even be necessary to load lines that don't match the criteria.

....Line.Load (WsNum, EntNum)
If however it does, load it up to be processed.

....for PntNum in range (Line.GetNumXyz()):
Same idea but this time for every point number up to the limit of the number of xyz coordinates do the following.

......(x, y, z) = Line.GetPoint (PntNum)
Get the position.

......(p, a, c, f, m) = Line.GetPointFlags (PntNum)
Get the flag data, these are extremely useful and important at times for now they will just be preserved, and straight out of the Vr documentation are....
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)

......Line.ChgPoint (PntNum, roundto(x,1.0), roundto(y,1.0), z,p, a, c, f, m)
Using the roundto function defined earlier the point is going to be changed to reflect the new x and new y.

....Line.ReRec()
Of course once it's changed it needs to be re-recorded.

....Line.Plot()
To really be elegant, the line should probably be erased prior to being changed and re-plotted. Many times however I like to to leave both versions on the screen (of course here there isn't any visual difference, I'm speaking in generalities) so I can see something happening or verify a change. A re-plot takes care of it.

Ws.UndoEnd(WsNum)
Close the undo and it's good to go.

Tuesday, January 23, 2007

All the way to the beginning

What if you have never learned to write a program in any language, or for that matter had the desire to do so. I'm not really going to post much today, other than to mention that I am working on a script I'm calling BeginnersVrPython.py. I am building it as a reference for anyone in the office that may want to jump right in programming in VrPython without any real knowledge of python. In my mind that is a bit of a limitation because to really get the most out of the language itself, well, you need to know a little about it. There are however an awful lot of things that can be done with even the most basic background. BeginnersVrPython.py will be a guided step through the simplest commands which should prompt the user to get into the VrPython documentation to expand the capabilities. It has always seemed to me that looking at examples, changing them a little, then taking off in varying directions, is the easiest way to learn programming. When done, the beginners script should offer just that kind of examples in a guided fashion. Eventually it will be posted somewhere, but until then I'll just email it to anyone interested in the current state.

Along the same lines of, going to the very beginning, there is another beginners ebook available called "How to Think Like a Computer Scientist: Learning With Python" available. I've looked over a little bit of it and it seems like a good read for someone very early in the game. If you are going to use this, install python 2.5 since that is where VrOne is at as of the current release (3.2 as of 01/07)

Thursday, January 11, 2007

Exactly the way you want it

There are times where Vr gives you all the tools you need to do something, but there may be just a little twist that still makes it worthwhile to write some quick code. In this case all that was necessary was to digitize some locations and have those written to a text file. There are several ways I can think of to do the exact same thing using tools that exist, but in this case there was no reason (or desire) to actually store points in the file, and there was a specific very simple output format desired (which of course could be easily modified for other purposes. This will also give us a reason to interact with a separate external file.

fprintf = lambda afile,fmt,*args: afile.write(fmt%args)

Gui=PyVrGui()

TextFileName='dig'
PointsFound=0
Stat=0

TextFile=open(TextFileName+'.txt','w')
while Stat==0:
..Stat,x,y,z=Gui.GetCoord('Dig_Txt')
..fprintf(TextFile,'%12.2lf%12.2lf%10.2lf\n',x,y,z)
..PointsFound=PointsFound+1
print '\n\a',PointsFound,' Points found and written to file ',TextFileName,'.txt\n'
TextFile.close()


Line by line.

fprintf = lambda afile,fmt,*args: afile.write(fmt%args)
This is mentioned elsewhere as one of my favorite tricks, it exactly reproduces the functionality of the C fprintf statement which formats text for output. Python has some very easy tricks for formatting, this just give me a familiar interface.

Gui=PyVrGui()
Always remember to create the Vr objects you are going to interact with, whether line, symbol, dtm, or in this case gui.

TextFileName='dig'
Points are going to be stored in a file, the name isn't important but let's put it in the block of code that is at the top and easy to find if someone needs to modify it.

PointsFound=0
Stat=0
These need to start at zero so we'll go ahead and initialize them as well.

TextFile=open(TextFileName+'.txt','w')
In this example we'll use the string addition method to add an extension which will end up creating a file named "dig.txt". This is just included to illustrate how if you have a base file name you can open several files with different extensions by just adding a different one. TextFile will become an instance of an open file object in "w" or write mode, there are several modes which mirror their c counterparts. Interacting with files is one of the more helpful actions to pick up. Here we will just be dumping formatted text to an ascii file, but there are times when it will be necessary to read and write from both binary and ascii files, all of which is controlled by the "mode".

while Stat==0:
GetCoord is going to return a status flag which is determined by if the user actually digitized a coordinate or hit End. This loop will just keep it running as long as they keep reading points, and to answer the most obvious question a status of 0 means yes a point was digitized; no I don't know why 0 is yes and 1 is no.

..Stat,x,y,z=Gui.GetCoord('Dig_Txt')
Digitize a point, as a side note the text string there is a title that is said to "show up in the VrOne message area". I have never actually seen it show up anywhere, but something is required to be there and I just enter relevant text in case it ever does.

..fprintf(TextFile,'%12.2lf%12.2lf%12.2lf\n',x,y,z)
The fprintf is discussed elsewhere but suffice it to say the text file we set up is going to get a piece of text formatted to 12 columns, 2 decimal places for each coordinate.

..PointsFound=PointsFound+1
print '\n\a',PointsFound,' Points found and written to file ',TextFileName,'.txt\n'
No real point, I just like to keep track of things like this and print the status when done.

TextFile.close()
Python is supposed to clean up after itself and close files when it exits, but as a rule it is a good habit of always doing the housekeeping explicitly.


Want to just see the least common denominator for a couple of the things mentioned.
To digitize a point, open pyedi and copy in
Gui=PyVrGui()
print Gui.GetCoord('')

Want to write something to a file, do the same (or open a python editor) with the following
file=open('/tmp/tmp.txt','w')#change the directory to something you have
file.write('a line of text')
file.close()

As an addendum, I want to plug a python forum that I mentioned earlier. I can even see it as a place that VrPython users could interact in a conversational way. I enjoy the forum so much that I tend to monitor it on a semi-regular basis and am likely to find a Vr related post fairly quick. Information is power, and sharing it has benefit for everyone. Check it out at The Scripts Developers Network or otherwise known as thescripts.com see you there!

Monday, January 08, 2007

Non-Programmer's Tutorial for Python

I don't really have time for anything earth shattering here, but if you are brand new to both programming, and Python I highly recommend the site "Non-Programmer's Tutorial for Python"

Friday, January 05, 2007

Need a new command?

A quick end of week freebie. Here is an example of a really useful program that a person could think themselves justified in griping that it doesn't exist in Vr. Emailing someone and asking for it, looking in each beta release hoping that a life of clean living and good fortune deems that your suggestion is incorporated. OR...... learn a little python and spend 5 minutes writing it yourself. Sure we could each come up with a hundred examples of tools we would like to see, the problem is that half the suggestions on each list would be unique, to which each would say, "How can you want that, this idea is much more important" while at the same time the poor beleaguered programmers struggle to keep up with dumb questions ( note: comic aside which I only mention because I have a string of about 3 for 6 that were total time wasters on the order of "what do I do with the 3 prong cord hanging out the back", to which the answers were still gracious and helpful)

User question: What would it take to write a command that would essentially be insert line in which you ID a line to inherit properties from then start digitizing with the those properties?

How this works is fairly obvious, let me just explain what I think the more elegant points of python are.

Line = PyVrLine ()
Gui=PyVrGui()
Line.Id()
lay,gp,wid=Line.GetLayer(),Line.GetGpoint(),Line.GetWidth()
Gui.PushKeyin ('inslin,lay=%d,grp=%d,wid=%d'%(lay,gp,wid))

We're going to need a Line(), and a Gui() object, then grab the line to inherit from using Id(). What I love is that on the next line we are declaring 3 variables and setting them to the proper values at the same time without ever worrying what kind of variable they are. Then sending the inslin command and all the keyins on one line using python text formatting. So the answer to "what would it take?", not much, cool eh.


Thursday, January 04, 2007

Think ahead and start right

Let me back off a bit and offer some general opinions that may save some time in the end. When I first started playing around with python, it was primarily porting old programs from other languages or mapping utilities from Vr's predecessor. It became almost a game to discover how few lines I could accomplish a task in, and how many basic utilities I could port. After the dust settled a little I realized that I was hooked and would be doing most (if not all) my future programming in python. At this point organization became much more important, but I also had some catching up to do. Here are a few things that helped that I would recommend, pardon as I randomly throw thoughts around.

First, develop a coding style that is consistent. One of the things I like about Python is the ease of use, as a result I am constantly passing code around and encouraging people to use, modify, and learn it. As things get passed around, or when you come back to it months later, a consistent style really helps. It has even made it possible to write a program that parses a script looking for certain cues and then writes an html documentation file for it. As examples of coding style the following aren't necessarily the best way to do things but at least I try to do them consistently and they seem to help.

  • Keep small reusable code snippets in a module for that purpose. For example I mentioned a Python incarnation of a C-like printf function, I keep that in my personal util module.
  • When importing and calling a module, I always use the full module name, for example if my printf function is in my dsutil module, I import dsutil then call dsutil.printf() rather than doing a from dsutil import * then just calling printf(). This is a small thing but even for standard modules it always shows where something is coming from.
  • Have some rudimentary version control. I have a function that prints the program name and last date modified at run time. Very basic, but at least if there is a problem I can ask the user what copy they are running.
  • I always put a small block of text at the beginning of a script to make notes in, import all necessary modules next, then declare and initialize what I call "variables of interest" that either need to have a particular starting value or which a user may look for at a later date. If there are more generic variables that are just needed to hold values at run-time I generally let those get created in situ. A good example of this would be something like x,y,z=Sym.GetCoord() where there is no need to initialize the coordinates, just let the function create and set them.
Second, document everything. As mentioned above it doesn't have to be fancy but it is amazing how quickly I forget what I was trying to accomplish with a perfectly functional piece of code like.
os.path.splitext(os.path.split(FullName)[1])[0]
A little reminder goes a long way next year when something similar is necessary.

Maintain a simple database of programs written. Just a simple delimited file that contains things like name, function, misc notes, state of completion. With that available I wrote another fairly simple script that parses that file and creates an html document with everything in a table, and links to the files created by the self documented files. In the end if everything goes correct, one program writes the documentation for the script, and another creates the index file from which it can be referenced all based on the information inside the program, or entered in the database.

And just so we don't go too long without a little VrPython code, here are several (though not nearly all) ways to do the old "hello world" program from within Vr. Yes this should have been the very first post, oh well, copy and paste into pyedi and watch the fun begin.

Gui = PyVrGui()

print 'hello world' # to console
Gui.DspMsg0('hello 1') # top Vr status box
Gui.DspMsg('hello 2') # bottom Vr status box
Gui.DspShortMsg(0,'hello 3') # top small status box
Gui.DspShortMsg(1,'hello 4') # bottom small status box
Gui.MsgBox ('hello 5','window title') # dialog popup

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.