Saturday, April 07, 2007

Utility programs 2

Rather than retyping the code from the previous post, I'm just going back to the line with argv and proceed from there with some actual working code. This time I want to focus on getting the filenames that will be processed. Remember the test of argv was just to determine if any arguments were passed. Since this program doesn't take any, having something else appear on the command line indicates a lack of understanding so we printed some usage instructions. Now let's assume the user knows that if he types "python opp2txt.py" he will get every opp file in the directory processed. But how to build a list of file names? Today we'll just build the list and print the filenames to show that it worked. The magic we want is contained in the glob module, no I don't know why it is named that, but isn't that just about the coolest name around? Here is the code that could be inserted into opp2txt.py to grab filenames.

if len(sys.argv)==1:
....Filelist=glob.glob('*.opp')
....if Filelist==[]:
........print "There don't appear to be any .opp files in this directory"
........sys.exit(2)
....for File in Filelist:
........print os.path.splitext(File)
else: usage()


First let's look at glob. The module itself is fairly simple and can be found in the python module docs. The short story is that you can return filenames matching a pattern in a specified directory into a list of strings. Really the best way to illustrate is to try it. In your favorite python editor do the following.

import glob
glob.glob(r'\vr\hostdir\*.par')

This should return a list which contains the names of every .par file in that directory. Something like...
'\\vr\\hostdir\\drivefile.par', '\\vr\\hostdir\\editline.par',
The lower case r before the path name just tells python to treat the string that follows as a raw string and not to see the backslashes as special characters. You could also put in forward slashes without the 'r' since that is a valid path separator in python but the return list comes back as an odd mix of forward and backward slashes.

We are storing the returned list in a variable called Filelist. If there are no .opp files in the directory the list will be empty in which case we print a message to that effect and exit, I just picked a random exit code of 2 to indicate no files found.

Next the for loop will step through the list and process each name string in the list. The variable which represents the name string will be "File".

Meaningful processing will come next time, but for now we could just use the code "print File" to display each filename but let's take this opportunity to explore the extremely useful os.path functions. These are most helpful in separating components of full filenames into paths, extensions, base filenames, etc. In the program which will be run in a directory that includes the .opp files and will not contain a path component the list would look something like...
['1.opp', '2.opp']
and the splitext() will just return a name and an extension similar to...
('1', '.opp')
('2', '.opp')
which could be accessed individually using indexes
>>> print ('2', '.opp')[0]
2
>>> print ('2', '.opp')[1]
.opp
>>>
However to really have some fun lets go back to the previous example in the editor and try some examples with the name list returned from the vr hostdir directory. Here are some truncated examples of what would be returned. Take time to play with the os.path functions, there is some real usefulness there.

>>> for name in glob.glob(r'\vr\hostdir\*.par'):
... print os.path.basename(name)
...
drivefile.par
editline.par

or if we run it through splitext() after it has been stripped to basename.

>>> for name in glob.glob(r'\vr\hostdir\*.par'):
... print os.path.splitext(os.path.basename(name))
...
('drivefile', '.par')
('editline', '.par')

or how about just running splitext to get the filename so that you can create another file with the same name, but with a different extension.

>>> for name in glob.glob(r'\vr\hostdir\*.par'):
... print os.path.splitext(name)
...
('\\vr\\hostdir\\drivefile', '.par')
('\\vr\\hostdir\\editline', '.par')

or what about if you want to get the path name so you can create a file in the same directory.

>>> for name in glob.glob(r'\vr\hostdir\*.par'):
... print os.path.split(name)
...
('\\vr\\hostdir', 'drivefile.par')
('\\vr\\hostdir', 'editline.par')

or just test whether or not the file exists, of course all these do because we're just passing in existing file names, but you get the idea.

>>> for name in glob.glob(r'\vr\hostdir\*.par'):
... print os.path.exists(name)
...
True
True

Cool stuff, and we haven't even done anything useful yet, hopefully next time.



Friday, April 06, 2007

The forums are back!

I'll post more on opp2txt more this weekend but I just had to mention that the Vr Forums are back. As I understand it they aren't technical support forums so much as a gathering place for users to share information. In that sense if used properly they could be an invaluable place to discuss problems, questions, tips, tricks and other useful information. Check them out at http://www.cardinalsystems.net/forum/

Tuesday, April 03, 2007

Utility programs 1

Ok so I said I was going to talk about function key parsing and python dictionaries, and I will... eventually. I got off on a small tangent though and decided that it would be beneficial to start out talking about external utility programs first. There are probably many uses for a nice organized function key data set, both inside Vr and out. But lets start small. I love little command line utilities that take the place of actually working, as a matter of fact if I have a motto it would be "It beats working" and the more times I have reason to say that on any give day the better. Here is an example...

When I am working with ortho's there are times that I would like to have the photo centers plotted in a file. Because almost everything in Vr is stored in easy to read, easy to interpret text files it is a piece of cake to go into the opp files and pull out the xyz coordinates in any text editor. But do that on 100 separate files and you are bordering on work. What if you could just run a simple little command line utility that would give you a space separated listing of the photo name and it's coordinates. That my friend "beats working". Let's think through this program a few lines at a time, when it's all said and done I'll post the final code on the repository web site.

First for the preliminaries


# Lets start out with a docstring to explain what the program is for.
'''
Extracts photo center coordinates from all .opp files in a directory
and sends
the output to the screen, which can then be redirected into
a text file.

'''
# For now just trust me that these are the modules we'll need.
import os,sys,string,glob
# It's always a good idea to put in a usage function. In this case since
# (like it says) there won't be any arguments, will force the usage string
# to print if the user enters any argument at all.
def usage():
....'''
....Explanation text to print if anything is entered as an argument.
....'''
....print '\nUsage: python opp2txt.py'
....print 'There are no arguments, it will just look in the current '
....print '
directory for files with a .opp extension, strip the '
....print '
coordinates out of them and output them to the screen'
....print '\n\nListings can be redirected into a file.'
....print 'eg: python opp2txt.py > photos.txt'

....sys.exit(1)
# This is the main part of the program. First we'll make sure no
# arguments were entered. sys.argv is the command itself the program
# name is always the first argument, if there is only 1 thing in the
# list then no arguments besides the program name were passed.
if len(sys.argv)==1:
....print 'no code yet run\npython opp2txt.py -h\nfor help'
else: usage()

Ok that's it for now. Copy the above to a python file called opp2txt.py , replace the indent holders (....) with your normal indentation and run it with an argument and it will describe where we are headed. You need python installed and in the path. Type "python opp2txt.py -h" and the usage should print.

If you want to get a better idea what argv does just create a python script that contains nothing except
import sys
print sys.argv
Then run the script with different (or no) arguments.
More to come...


Friday, March 30, 2007

Solid gold

Ok I haven't posted in too long and this one is going to be really short, but I have some really fun plans along the lines of a function key parser, editor, etc. that will really push into the python dictionary data type.

For now though let me pass along huge kudos to the team at Cardinal. I often have thoughts of the guys slaving away in the salt mines at the Vr Development lab. Poorly used and un-appreciated for the efforts that spring forth. However in the latest patch versions (usual disclaimers about beta software, or anything that isn't in official full release, blah..blah..) there are a couple of nuggets of pure gold. One is attaching in fly-line, granted this doesn't have anything to do with python, but along with things like all the different snap options, overloading (I better stop before I get carried away) attaching a fly-line is one of those things that will make me pause and think "wow, I love this stuff". The python related gold nugget is Ws.IdEnt() which allows the user to just start a generic ID routine then select anything on the screen, or cycle through entity types. This is the same ID that is used for fasdel and others like it. Check it out on the python help pages, it is absolutely too cool.

I'll also toss in a little utility that I use to save the current layer display status. Disclaimer here is that it only works with 3.2 and above because before that PyVrLayer had it's own module. It could be modified to work like that, but this version doesn't.

Layer = VrLayer ()
Gui=PyVrGui()
# Initialize Vr objects to be used.
parfile=open('c:/vr/hostdir/save_lay_stat.par','w')
# Open a parameter file to save the layer status in. 'w' is write mode.
for laynum in range(1000):
..parfile.write("%d %d\n"%(laynum,Layer.Stat(laynum)))
Gui.DspMsg0('Layer state saved')
# I'm only going to save the status of the first 1000 layers, I rarely use any others.
# Then write the Stat() or current status along with the layer number.
parfile.close()
# Always close files when done. Python is smart this way, but it's good practice.



Friday, March 02, 2007

A touch of class

You can do an amazing amount of good with VrPython without knowing anything about Object Oriented Programming (OOP). For that matter most, or at least many things you can do without even knowing much about Python. In my opinion though, knowing more is always better than knowing less. Let's talk a little about how I understand OOP working out in Vr. To begin with there is a very good explanation of the basic concepts on Wikipedia. I could make up my own example of the biggies like class, object, inheritance but they do such a good job there that I'll just trust anyone who needs a basic understanding to read it (3-5 minutes tops).
Let's look at a line. In VrPython there is a class called PyVrLine. Any object which is created from that class has many attributes, characteristics, data (whatever you want to call it) which describes it's state. Examples of these attributes (the word I'll stick with) are it's layer, graphic pointer, width, coordinate list and so on. A line also has many methods, or functions it can perform based primarily on it's current state. When ever I create an individual object or instance of that class, I have access to all the attributes and methods that go with it. In Vr the individual object is created with the command
Line=PyVrLine()
I have often wondered if I am confusing things by using the word "Line" as the name of the particular object. Sure it is descriptive and relevant, but for example purposes it needs to be clear that I could use any name for the object being created which is of the class PyVrLine. It is just an identifier of the unique entity I'll be using just like Dennis is just a name used to identify this particular object of the Human class.

In any case, whatever we call a particular instance, once we have a PyVrLine(), the fun can begin. On of the most useful functions when starting off is Id(). Once there is a PyVrLine object, Id() will use the function built into it to bring up a familiar set of identification menu keys and wait for the user to click button #1. If the click meets all the criteria to find a line Id() will not only return the line number of the line it found, but will populate the PyVrLine object with all the attributes of the identified entity. It does the exact same thing as Load() but allows for visual identification in the current workspace. Once the PyVrLine object is loaded (or I suppose created from scratch with default values) the whole host of methods that can get, set, or compute information based on the current state become available.
The key is that a Vr object can't just be looked at even as a complex collection of data, but also as the family of functions that allow user interaction with it. This frees the developers to grant access to the power of the internal magic in a secure manner, and the user from having to understand what is going on (or having to re-invent the wheel for typical data interaction).
Ok I'm rambling and really need to bring this to a close. Besides the fact that OOP is really cool, and it helps to have a simple understanding of how it works out in Vr, what is the point? Is there any reason that a person might want to learn to use classes in everyday programming? I very rarely do, and most of the true object oriented modules I've written were done because I wanted to give it a try, but here is an example. I have a module which contains an angle class. An object created from this class of course stores the value of the angle it represents, but it also contains methods that allow it to represent itself in radians, degree-minutes-seconds, decimal degrees, bearings with quadrant, any of the above in varying text formats along with an overloaded repr(). Along with this there are methods that allow for conversion between the varying systems. All these things are easy enough to do on the fly, but a consistent interface and descriptive names make programs that use the class easier to read, and playing with basics like this makes understanding modules written by really intelligent programmers a bit easier to follow.

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.