Showing posts with label Basics. Show all posts
Showing posts with label Basics. Show all posts

Friday, February 14, 2025

Almost sometimes biennial proof of life.

 Recent conversation about a particular legend in a particular field went something like "Did you realize he was that old?" and my first thought was "I didn't even realize he was still alive!".  Makes me wonder how many people think that when the look at such a long-ignored blog.  Well I'm not that old and I am still alive and the question I was asked was "Are you still writing any python?", to which the answer is yes almost daily but I also realize that no one really cares.

I did pass off my "BeginnersVrPython3.py" script to someone and realize that I should make a quick video to document what it does in case anyone in the office wanted to start plinking on coding some python.

It can be found at

https://rumble.com/v6ezy07-cardinal-systems-vrone-vrtwo-08-beginning-vrpython.html

and the script can be found at

https://drive.google.com/file/d/1Kyt0bkb0qPEAgtl7HriiFfjVmt7s0dzh/view?usp=sharing

Feel free to contact me at the email in the script if you have any questions.

Tuesday, September 25, 2018

Modifying LiDAR Intensity in Vr Applications

I actually don't have the patience to look back through posts with regard to modifying LiDAR data so I'll just drop this one in as an example of how simple it can be.  This will have limited usefulness because it is fixing an apparent shortcoming in Vr that will no doubt be rectified at any moment, but hopefully the example will continue to be useful.  The current problem is that points created via dsmare (DSM Area) have a nice RGB value, but sometimes it would be nice to have Intensity as well. Rather than making it tricky I found that if I just add the R, G, and B values and divide by 3 it provides a useful "intensity like" display.    
There are only a couple lines that do anything productive so I'll just explain them.

print 'rgb2int.py modified 12:23 PM 9/25/2018'
# Set point intensity based on rgb
'''
License: cc-by-sa  http://creativecommons.org/licenses/by-sa/3.0/

'''
Ws=PyVrWs()
Punt=PyVrPunt()
Gui=PyVrGui()
WsNum=Ws.Aws()
PointBufferCount=Ws.GetPuntBufCount(WsNum)

Gui.ProgInit('Points',Ws.GetPuntBufCount(WsNum))
for PointBufferNumber in range(PointBufferCount):
    Punt.Load(WsNum,PointBufferNumber)
    Gui.ProgSet(PointBufferNumber)
    for PuntNum in range(Punt.GetCount()):
        x,y,z,PuntA,r,g,b= Punt.GetPuntRgb (PuntNum)
        PuntA['Int']=int((r+g+b)/3)
        Punt.ChgPuntA (PuntNum, PuntA)
    Punt.ReRec()
Gui.ProgReset()
PyVrGr().Replot()
 You need to have a PyVrPunt object to work with.  This object will be used to load up a "Point Buffer" full of LiDAR data which we can then step through the individual point data.  Note that you need to loop over the whole file loading each of the point buffers into the Punt object. This is done with the first for loop

for PointBufferNumber in range(PointBufferCount):

As we step through all the buffers, each one needs to be loaded and then it will be stepped through loading up the individual points hence the next for loop.

for PuntNum in range(Punt.GetCount()):

There are several ways to get particular data items from individual points and much of the data is stored in an attribute dictionary known as PuntA.  Many times I just load up PuntA, query it, make decisions, then make changes based on what I find.  In this case it is handy that the method that extracts the R,G,B data also pulls out PuntA at the same time because Intensity is stored within PuntA.  The first thing that needs doing is grabbing the data for the individual point.


x,y,z,PuntA,r,g,b= Punt.GetPuntRgb (PuntNum)

Then we just turn right around and push the intensity into the current PuntA dictionary and record the modified attributes back to the point.


PuntA['Int']=int((r+g+b)/3)
Punt.ChgPuntA (PuntNum, PuntA)


Once all the points are changed in the currently loaded buffer (Punt), the whole buffer can be re-recorded.


Punt.ReRec()

The Punt class is fully documented in the Python Programming section of the documentation but it basically comes down to knowing if you are creating or modifying something inside the attribute dictionary or outside it and working with the appropriate methods.

Wednesday, April 01, 2015

You gotta love a good library

The Columbus Metropolitan Library is arguably one of the best libraries in the nation.  I use it so much I have had my library card memorized for over 25 years.  The idea that there is information and services there at my fingertips any time I want is fantastic.  I was thinking about this yesterday when I started pulling functions out of PyVrGeom().  Libraries (modules) can be everything from little bits of code snippets that you want to reuse over and over to complex collections of classes that you could only dream up but never realistically code yourself. 

For example PyVrGeom is just a collection of math that some would find easy enough to code, but why do so when somebody has done the heavy lifting and offers you the calls to do it with some preexisting code.  In this case very professionally created and presented, but it is just as easy to hack together some snippets that just save you typing.  For example when I was working on a KML export routine I didn't need to learn something complex and universal (which libs probably exist), I just knew what it looked like and wanted to replicate those entities by passing along a few basic parameters.  In this case I slapped together some code to create the KML entities, but at the same time the coordinates needed to be in geodetic coordinates so I had to grab a projection library that someone way sharper than me had already put together. Then it struck me that a KMZ is just a file zipped using the standard algorithms so if I import that one I can just as easily write both KML and KMZ files.

I'm not going to offer any code this time just another reminder that there are some good reasons to install python if you are going to do some scripting.  Here are some of the libraries that I really enjoy and use often.

pyproj - for converting local coordinates to global (or from one to another).
liblas - for all things LiDAR related.
zipfile - for reading and writing compressed files.
xlrd/xlwt - for directly reading and writing Excel spreadsheets.

Along with some of the standard system libraries that you can find a million uses for like sys, os, time, and math.

And here is a tip I'll add for free, check out http://www.lfd.uci.edu/~gohlke/pythonlibs/


Wednesday, March 11, 2015

5 Minute Functions - 2

I haven't stopped coding, I've just stopped talking about it.  Again more a function of the fact that I doubt anybody really cares.  I noticed the other day that I have slightly over 300 python programs that have accumulated over the years.  Certainly there are the ones I am really proud of like the ones that translate a Vr file into a KML file or a TIN into  LandXML format because of the crucially useful functionality they add.  Then there are the ones I use multiple times an hour, like double points on a line, drive to points by user specified parameters, storing helpful window states, adding clamping points to DTM lines, and on it goes.  Lately I have noticed that I also just stop sometimes and write a quick program because as I mentioned in a previous post of a similar title, I can spend 5 minutes programming and save 15 minutes (and lots of wear and tear on the mouse clicking finger).  Not only is it worth it but it is also fun, keeps my brain lubricated, and gives me a base in case I find the task useful and want to come back later to add options, dialogs, or arguments that would make it more universal.

Here is an example, I have a few thousand points that got dumped into a file without regard to any distinction.  about 300 of them would be really helpful if they were in a certain layer and I don't want to miss any of them.  The only thing that makes them different is that there is a common string of text inside the feature code.  There may very well be an easy way to do this with Vr, but after a couple of tries I couldn't come up with a parameter that worked so I thought, "why not just write a quick script?".  In this instance I'll just hard code the search string and resulting changes. If I find it useful I may come back later and add a simple dialog to make it more agile.

print 'fcglob.py modified 6:23 AM 3/11/2015'
# Globally change something based on text in it's feature code
'''
Copyright 2015 Dennis Shimer
No warranties as to safety or usability expressed or implied.
Free to use, copy, modify, distribute with author credit.

Simple beginning of a function to change entities based on their
feature code.

Variables of interest:
    None
'''
Ws=PyVrWs()
Sym=PyVrSym()
WsNum=Ws.Aws()
Ws.UndoBegin(WsNum,'fcglob')
for EntNum in range ( Ws.GetSymCount(WsNum)):
    Sym.Load (WsNum, EntNum)
    if Sym.GetFc().lower().count(' fh'):
        print EntNum
        Sym.SetLayer(1042)
        Sym.SetGpoint(21)
        Sym.ReRec()
print ('\a')
Ws.UndoEnd(WsNum)

 It started out as about a half dozen lines in pyedi and doesn't amount to much more now. Nothing fancy, just something I can come back to if I need it again, and in this case it really helped.

I don't know, maybe I'll just start dumping more of these here. Doesn't hurt anything and though most of them aren't universally interesting it might give somebody an idea.

Tuesday, August 27, 2013

Let me look that up in the dictionary for you.

There have been some changes to the VrPunt methods over time.  I have suspected that not everything is implemented exactly the way it is documented, and sometimes a name could be different or missing.  I have discovered though that the real magic is just in working with the dictionary which stores all the relevant LiDAR point attributes.  The LiDAR data provider that we most often use stores the individual flight line tag in the "SOU" tag, Vr however will only filter by the "FLT" tag.  Since FLT isn't used in our data, changing it to reflect the SOU value doesn't seem to have a downside.  Below is a simple program that runs through all the point data and copies SOU to FLT.  Now I can use the flight line filter to turn the points on and off.

print 'souflt.py modified 12:44 PM 8/27/2013'
# Copy LiDAR Source attribute to Flight attribute
'''
Copyright 2013 Dennis Shimer
No warranties as to safety or usability expressed or implied.
Free to use, copy, modify, distribute with author credit.

Reads the "Source" in a PuntA dictionary and sets the "Flight" to the same value.

'''


Ws=PyVrWs()
Punt=PyVrPunt()
Gui=PyVrGui()
WsNum=Ws.Aws()
PointBufferCount=Ws.GetPuntBufCount(WsNum)

Ws.UndoBegin(WsNum,"Source2Flight")
Gui.ProgInit('Points',Ws.GetPuntBufCount(WsNum))
for PointBufferNumber in range(PointBufferCount):
    Punt.Load(WsNum,PointBufferNumber)
    Gui.ProgSet(PointBufferNumber)
    for PointNum in range(Punt.GetCount()-1,-1,-1):
        PuntA=Punt.GetPuntA(PointNum)
        PuntA['Flt']=Punt.Sou(PointNum)
        Punt.ChgPuntA (PointNum, PuntA)
    Punt.ReRec()
Gui.ProgReset()
PyVrGr().Replot()
Ws.UndoEnd(WsNum)
 So the key is to load up the PuntA to make sure it is populated with the correct values, then modify the particular attribute and save it back to PuntA. When all the points are done re-record the entire Punt buffer.  As of this writting the dictionary is organized as follows.

"Lay"     = 1   - Layer                    (1-30001)
"Int"     = 0   - Intensity                (0-65535)
"Dsp"     = 1   - Display flag             (0-1)
"Ret"     = 0   - Return number            (1-5)
"Nre"     = 0   - Number of returns        (1-5)
"Sdf"     = 0   - Scan direction flag      (0-1)
"Edg"     = 0   - Edge of flight line flag (0-1)
"Cla"     = 0   - Classification           (0-31)
"Syn"     = 0   - Synthetic flag           (0-1)
"Key"     = 0   - Key-point flag           (0-1)
"Del"     = 0   - Delete flag              (0-1)
"Ang"     = 0   - Scan angle               (-90 - +90)
"Flt"     = 0   - Flight number            (0-255)
"Sou"     = 0   - Point source Id          (0-65535)
"Red"     = 255 - Red component color      (0-255)
"Green"   = 255 - Green component color    (0-255)
"Blue"    = 255 - Blue component color     (0-255)
"GpsTime" = 0.0 - GPS Time                 (Double precision number)

Friday, November 16, 2012

Projecting some thoughts about pyproj.

So I've been playing around with pyproj a little lately and have come up with a couple new thoughts.  As always I'm typing as I go so not everything is a clean example of good code or procedure, just something I've run as a test of my thoughts.

First, the application; below is showll.py which when run displays the lat long of the current cursor position in the DspMsg0 area (the top long line right below the command area).  I decided to do this as an app so it is running in real time and clicking the buttons just performs specific tasks.  Speaking of the buttons, B1 or the left mouse button prints the current position in the local coordinates, long lat, and notes the projection used. Something like

1369447.29077 471184.7857 (-84.66858719560254, 40.93954025528449) epsg:3753

In this part of the world we primarily deal with two projections so I kept it simple and just used B2 or the right mouse button to toggle between the two.  It would have been easy enough to bring up some kind of selection dialog, but for now I only need the two. The current projection is also displayed in the DspMsg area.

I recently discovered why I always had to send and receive meters even though the EPSG code clearly specified at foot based projection.  It was by design so there was never any question what was expected or returned.  The answer was always meters, however the code has since been modified to include an init keyword

preserve_units=True 

which handily enough, does exactly what it says. I understand the value of the consistency, but appreciate the option. I have also discovered in testing that you can take a proj4 definition and paste it straight into an object init function in quotes and get the exact same functionality as using the EPSG codes.  For example...

ohs=pyproj.Proj('+proj=lcc +lat_1=40.03333333333333 +lat_2=38.73333333333333 +lat_0=38 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs' )

Well in any case here is the app, it isn't worth much except by way of example, but pyproj itself is really handy when working with things like flight prep or control layouts when you want to give somebody geodetic coordinates to work from.

print 'showll modified 12:17 PM 11/14/2012'
# Show Longitude Latitude of current position.
'''
Copyright 2012 Dennis Shimer
Vr Mapping copyright Cardinal Systems, LLC
No warranties as to safety or usability expressed or implied.
Free to use, copy, modify, distribute with author credit.

Only two projections are supported. Both are NAD83(HARN) / Ohio (ftUS).
EPSG:3753 for North Zone
EPSG:3754 for South Zone
Button 2 (right mouse) toggles the projection, Button 1 (left mouse)
will print out the current position in local coordinate and it
Longitude, Latitude decimal degrees.
'''
MYAPPID = 10011

Gui = PyVrGui()

import pyproj,sys

global Projection

Projection=pyproj.Proj(init='EPSG:3754', preserve_units=True)
Gui.DspMsg('OH S EPSG:3754')

def MyAppDigCB (x, y, z, key, id):

 global Projection
 Gui.DspMsg0(repr(Projection(x,y,inverse=True)))
 if (key == 1):
  print x,y,repr(Projection(x,y,inverse=True)),Projection.srs.split('=')[1]
 elif (key == 2):
  if Projection.srs.count('3754'):
   Projection=pyproj.Proj(init='EPSG:3753', preserve_units=True)
   Gui.DspMsg('OH N EPSG:3753')
  elif Projection.srs.count('3753'):
   Projection=pyproj.Proj(init='EPSG:3754', preserve_units=True)
   Gui.DspMsg('OH S EPSG:3754')
 elif (key == 11):
  MyApp.Close ()
  MyApp.StopRunning ()

def MyAppKeyCB (key):

 MyAppDigCB (x,y,z, key, 0)

try:
 MyApp = PyVrApp (MYAPPID, "ShowLL")
except:
 print "Could not start application:", sys.exc_info()[0]
 
try:
 Mk = PyVrMenuKeys ("ShowLL", 10)
 Mk.SetMkLabels ("ShowLL","1 Print pt","2 Tog N/S", "3", "4", "5", "6", "7", "8", "9", "*", "0", "# Quit")
except:
 print "Could not create menukeys:", sys.exc_info()[0]

try: 
 MyApp.SetDigCB (MyAppDigCB)
 Mk.SetKeyCB (MyAppKeyCB)
 MyApp.KeepRunning ()
 
 Mk.Delete ()
except:
 print "Exception during application:", sys.exc_info()[0]
 Mk.Delete()
 MyApp.Close ()
 MyApp.StopRunning ()

How about one more bonus snippet with regard just switching from on projection to another. Thanks to Proj4 and pyproj, this is just how easy it can be.

>>> ohs=pyproj.Proj(init='EPSG:3754', preserve_units=True)
>>> ohn=pyproj.Proj(init='EPSG:3753', preserve_units=True)
>>> pyproj.transform(ohs,ohn,1759595.61,661026.83)
(1759551.3008025475, 54039.04746006191)

Wednesday, April 29, 2009

The 5 minute function.

No I'm not actually going to put a lot of time into a post since there doesn't really seem to be much interest in what's going on. However a recent conversation in the Vr Python Forum is a perfect example of the whole purpose of having Python in Vr. If you are a member you can check it out there.

http://www.cardinalsystems.net/forum/index.php?topic=51.0

The basic premise is that you need a simple function to automate a fairly simple task, in this case labeling all symbols in specified layers with a piece of text in which the label is the symbols feature code. It could be any attribute, or any entity type, but the point is; rather than waiting for the feature to show up in Vr, with a little practice, you can write it yourself (usually from existing snippets) in about 5 minutes and be on your way.

Friday, June 08, 2007

Life Outside Vr 3 (well not very far outside)

Actually not at all, but the point isn't that we need to work outside Vr, it is that there is a whole world of files available using some straight python functionality. Now that we have a little file opening and reading under our belts, lets come back into Vr and bring a little of that technique with us. Let's also work on tightening up the code a little. If you are fairly new to VrPython, I think you'll really like this one. We are going to let Vr tell us the name of the file to open (in this case the current function key file) then using standard python we'll open, read, and parse the data we want out of the file, then use Vr to build a prompt box. For this example the data we want is all the function key names, and the prompt box will allow us to pick a function key name. Why? Well how about if you were going to write a parallel function using the existing inspar command but wanted to change the PARMOD= to "Function Key" and wanted to select the function key from a list then send it using the FKEY= key-in. If so, here is one way to get the function key name. It is rather long and convoluted but I'll try to make it worth what you are paying for the look.

Cfig=VrCfg()
# We'll need a Vr config object to access the name of the currently
# loaded function key file name.

FileName=Cfig.GetFkeyFileName()
# Grab the name.
FkeyFile=open(FileName,'r')
# Open the file
FileData=FkeyFile.read()
# We're going to read the file twice. This time as a string so we can count the
# instances of the key-word "KeyName" to figure out how many entries our
# prompt box will need.
NumberOfFunctionKeys=FileData.count('Fkey KeyName')
# Use the string method count to do just that.
PromBox = VrPromBox ("Function Keys", 60, 1)
# Create a PromBox object.
PromBox.AddList ("Select a Function Key",NumberOfFunctionKeys , 40, 0)
# I'll skip helpful text and just get right to the listbox of length equal to
# the number of function keys.
FkeyFile.seek(0,0)
# Rewind the file to read it again.
FileData=FkeyFile.readlines()
# This time read it as a list, each element of which is a line from the file.
for FileLine in FileData:
# For each line in the data list.
..if FileLine.count('Fkey KeyName') != 0:
# Test to see if it is a line containing the function key name.
....FkeyName=FileLine.split()[2]
# If so split the line data into the three words
# 'Fkey','KeyName','ActualName' which is in itself a list, the third element
# of which ( [2] ) is the actual name of the function key.
....PromBox.AddListItem(FkeyName)
# Add that to the list of items to be displayed.
FkeyFile.close()
# Could have closed it after the readlines() but just go ahead and do it now.
if (PromBox.Display(0) == 0):
..FunctionKey=PromBox.GetList (0)
# Standard prompt box usage to grab a list item
print FunctionKey
# and print it out (or use it in an interesting way)

Now as far as tightening it up. I could have read the file only once and built a for loop to count the instances of the KeyName key-word, but I'm going to go in a totally different direction. I'm still going to read the file twice, as a matter of fact I'm going to open it twice. The goal of this exercise is to get as few lines of code as I can. I'm not going to profess that this is the tightest and most efficient. It just has a lot less lines of code and is intended to make you think. Look at it first, then read the explanation.

PromBox = VrPromBox ("Function Keys", 60, 1)
PromBox.AddList ("Select a Function Key", open(VrCfg().GetFkeyFileName (),'r').read().count('Fkey KeyName'), 40, 0)
for FileLine in open(VrCfg().GetFkeyFileName (),'r').readlines():
..if FileLine.count('Fkey KeyName'):PromBox.AddListItem(FileLine.split()[2])
if (PromBox.Display(0) == 0):print PromBox.GetList (0)


Line 1: We have to create a prompt box object because I can't find any way around it and we'll need to use methods of this specific object in the next line.

Line 2: Let's take this a step at a time.
A) Add to the list using the formula AddList (Prompt, NumItems, Height, DefaultItem) Prompt is set to "Select a Function Key", NumItems is the tricky part, Height is 40 and DefaultItem is 0.
B) Look at the NumItems formula and read it as follows
Open a file using the string returned by a VrCfig() object method GetFkeyFileName in read mode ( 'r' ) which open file object you should read() the data, of which data you should count() the number of occurrences of 'Fkey KeyName'. Notice that when all this is said and done it just give a number which represents the number of function key names and therefore the number of function keys

Now that we have a prompt box which contains a list set to the length equal to the number of function key names, let's populate the list with those names.

Line 3: For each element in a list, the list comes from opening the same file again, in read mode again, but this time instead of read() to get one solid string, we use a method that will return a list which is exactly what the for loop is working on. We are looping on a list that was created and populated just for the loop, each element of which is a line from that file.

Line 4: FileLine represents the line of data out of the file which is being examined as we loop over the entire list of lines. count() tells how many occurrences of a work exist in the string 0 means none which in terms of an if means false so any other number that comes back means true (the string exists, therefore it is a function key name). if it is true we want to add the actual name to the list. The actual name as we saw above means splitting the string, which creates a list, from which we want element [2]. So add to the list the third word which is the result of splitting the string because it does (true) contain the phrase 'Fkey KeyName'. I didn't add another line and indent it because there is only one statement to execute if true which allows you to just put it after the colon and be done with it.

Line 5: Same idea, standard prombox syntax, but since there is only one action, just put it on the same line. Ugly code but remember my only goal was to reduce the line count. And why put the string in a variable just to print it on the next line, print it as it returns from GetList()

And I haven't even taken the time to explain the forum post regarding reading and parsing aerosys .pxyzopk files, a great example of the kind of data we mappers work with all the time. Maybe next time.

Monday, May 21, 2007

Life Outside Vr 2

Ok I have a few minutes so let's look at actually reading some data out of an open file. We'll stick with ascii files for now. There are so many fun reasons to work with files outside Vr that it would take too long to enumerate all of them but a few of my favorites include.

  • Reading info out of an ImageUtil project and creating all the Vr2Ori project and all models with 1 click.
  • Converting all Vr2Ori and VrAt files to or from their corresponding equivalents in orientp3 (yes we still use it and get along fine) so a setup in the analytical equals a setup in softcopy or vice versa.
  • Reading a PATB and plotting it graphically in VrOne for visual analysis and blunder checking.
  • Reading and plotting AT results including photos, layout data, computed extents and photo scale based on image centers and control averages.
You get the idea, there are just so many files out there that can be of easy use with a click or two. Sometimes there is already a way to do the same things, but a custom program allows you to set things up exactly the way you want without any further intervention. As in the last post we'll be working with vr.cfg in read only mode because it is a file that exists on everyones system (if you are working in Vr, which I assume) and has the same basic patterns. Again I will suggest you work in some kind of python IDE, I suggest pythonwin but you could even work from the python command interpreter if you want.

Let's start by creating a python file object like we did last time, it will be called f (for file).
f=open('/vr/hostdir/vr.cfg','r') # 'r' means read mode.
now if I
print f
there should be some obscure text describing the object. Hopefully there was some time to look over the file object docs to study up on the simple methods, let's look at a few.
Read one line out of the open file
>>> f.readline()
'# VR Configuration File\n'
I should note here that once it reads the line, the address in the file that python is sitting at is the beginning of the next line so if you do it again
>>> f.readline()
'# FileName : c:\\vr\\hostdir\\vr.cfg\n'
if for some reason it is necessary to go back to the beginning of the file we need to "seek" the new position. seek takes two arguments "offset" (how much to jump from the position passed in) and "whence" (where to start the offset). "Whence" defaults to the beginning of the file, so if you want to go the the beginning you can say "see" with an offset of 0 (don't move at all from the position passed in) and a whence of 0 (or if you pass nothing for whence this is the default) so
f.seek(0,0) # 0 offset, 0 whence
f.seek(0) # 0 offset, default whence which is 0
will both take python to the beginning of the file, so do that then readline() again and you should see the first line again. Let's read that first line and store it as a string we'll call "s"
>>> s=f.readline()
>>> print s
# VR Configuration File
It would of course be possible to do this with each line of the file, though there are better methods to be discussed later, but lets look a what we could do with that string now that we have it.
I can split it based on white spaces (the default for split) which returns a list of the words in the string.
>>> s.split()
['#', 'VR', 'Configuration', 'File']
For that matter we could split it based on any character like commas, or in this case the "i"
>>> s.split('i')
['# VR Conf', 'gurat', 'on F', 'le\n']
Note that splitting on white spaces takes off the newline character but splitting on a printable character doesn't. Of course there are plenty of ways to take the newline off, but just take note for now. How about if we needed to test for a sub string inside the string, well let's count occurrences.
>>> s.count('i')
3
>>> s.count('ig')
1
>>> s.count('z')
0
So a test would look something like
>>> if s.count('ig') : print 'yes'
...
yes
I didn't say if s.count equals something or is greater than something because I just wanted to know if it was non zero and anything non zero is true for the if test.

There is no sense typing out all the things you can do with strings because they are documented in the python docs, it is worth noting that there used to be a string module, but now each of these methods is a built-in which is why there was never an import statement above.

Let's go back to the split step and put the resulting list in a variable called "l", then we can loop over the list and do whatever we want, in this example just print each item.
>>> l=s.split()
>>> l
['#', 'VR', 'Configuration', 'File']
>>> for item in l:
... print item
...
#
VR
Configuration
File
or grab individual elements of the list.
>>> l[3]
'File'
or test to see if the list contains certain values.
>>> l.count('File')
1
>>> l.count('Nothing')
0
Like strings I'll leave the explanation of lists and their methods to the professionals, in this case the python tutorials.
But I'll leave with a couple of thoughts:
First, check out readlines() it will come in handy and will probably bear heavily in the next post.
Second, one of the real beauties of python is it's ability to get a lot done with a little code, this can be accomplished by taking whatever data a method returns and acting on it with the appropriate method. Consider the following lines typed into the interpreter.

open() is going to return a file object with all it's accompanying methods.
>>> open('/vr/hostdir/vr.cfg','r')
obscure text describing an open file object

let's just tack the readline() onto the end and let it return the first line which is a string.
>>> open('/vr/hostdir/vr.cfg','r').readline()
'# VR Configuration File\n'

It it is a string we can split it to get a list of words that were included.
>>> open('/vr/hostdir/vr.cfg','r').readline().split()
['#', 'VR', 'Configuration', 'File']

Of course the list is made up of elements that can be addressed lets get the second element whose index is 1.
>>> open('/vr/hostdir/vr.cfg','r').readline().split()[1]
'VR'

Which is a string and can be forced to lower case using the lower() method.
>>> open('/vr/hostdir/vr.cfg','r').readline().split()[1].lower()
'vr'

Is this stuff cool or what.


Thursday, May 10, 2007

Life outside Vr

I have had several folks ask about opening reading and parsing external files. There are so many applications that I don't even know where to begin. Whether plain ascii text files, or binary, python is loaded with tools for doing what you want or need. I think I'll just start rambling and see where we head. One thing that you will notice immediately is that there can be literally dozens of ways of accomplishing the same task. I'm not going to pretend that what I am putting forward is the best or most efficient, I'll just mention some tools that come in handy quite often. For today lets just open a file and grab some data from it. Let's pick a file that everyone should have, that being your Vr configuration file \vr\hostdir\vr.cfg. Don't worry, for now we'll just open it in read only mode. In your favorite python editor or pyedi within VrOne type and run the following.

f=open('\vr\hostdir\vr.cfg','r')

Ok, I'll admit that was a trick, it shouldn't have worked, even though everything about the command is totally correct, except that is for the backslashes "\". But wait you say, we are working in windows, and the backslash is what is always used to specify path hierarchy, and you would be correct. The problem is that the backslash is a special character in python (and several other languages, not to mention unix). Python uses a forward slash to separate path elements, or to use backslashes you have to tell python you are sending it a "raw" string and that it should not treat the backslashes as escape characters. I don't start here to be bothersome, but it is important to understand that python expects certain things and may send back something you aren't expecting if you aren't careful. Note the paste of a session where I open the file using 3 valid commands, look at what comes back, and realize that this is outside VrOne in the PythonWin editor.

First I open it with forward slashes because I grew up on unix, the forward slash is always in the same place no matter what keyboard you have, it is what python prefers, and I just like it better. I'll use "f" as the open file object and will open it, display it, and close it. The 'r' means to open it in read-only mode, we'll talk about modes later but I digress.

>>> f=open('/vr/hostdir/vr.cfg','r')
>>> f
Text describing file object which blogger deletes for some reason.
>>> f.close()

Next we'll use backslashes, but they have to be protected by another character so they aren't evaluated as an escape sequence, by coincidence the character that does this is a backslash so each occurence must be entered as
>>> f=open('\\vr\\hostdir\\vr.cfg','r')
>>> f
Text describing file object which blogger deletes for some reason.
>>> f.close()

Next we'll use the backslashes but we'll tell python that the name string is a raw string and that the backslashes shouldn't be evaluated as anything else. Just put an r in front of it and we're good to go.
>>> f=open(r'\vr\hostdir\vr.cfg','r')
>>> f
Text describing file object which blogger deletes for some reason.
>>> f.close()

It is worth noting that if you grab a filename inside Vr using either of the following, it returns forward slashes.
print PyVrGui().OpenFileNameDialog('title','c:/vr/hostdir','*.cfg')
C:/vr/hostdir/vr.cfg
print PyVrGui().OpenFileNameDialog('title','c:\\vr\\hostdir','*.cfg')
C:/vr/hostdir/vr.cfg

That will do for now. Yeah it's pretty slim, but I need to go and we'll actually read something next time. For now though it wouldn't hurt to get familiar with some of the python file methods, from the python docs. You can also get a little more information on the open function from the built-in functions docs (just scroll down to open()), this will come in handy when we move to appending, writing, and binary files.

Monday, April 16, 2007

Utility programs 3

Ok, time flies and all, but we better get some actual working code in this thing. In post 1 all we did was create a usage string and test argv to see if anything had been passed in (in this case we don't want anything). In the second we used glob to build a list of files matching a certain extension in the directory we're working in. Eventually we could us a windows file select dialog but for now this will be a simple command line utility. Let's get right to the pertinent section then put it all together in the end. Using th same indentation as where we left off, lets replace

........print os.path.splitext(File)

which was just used to show that something was working and to illustrate os.path functions with

# Step through the list of files processing each
....for File in Filelist:
# Each file will need to be opened
........OppFile=open(File,'r')
# readlines() reads every line in a file, returns a list in which each member
# of the list is a line in the file, including the end of line character.
........FileData=OppFile.readlines()
# Step through the list of lines in the open file
........for Line in FileData:
# This isn't real tricky or elegant, but it turns out that the coordinate
# positions stored in the .opp file are on lines which have an "l" as the
# second character, so let's just determine first of all if the data line
# is a coordinate
............if Line[1]=='l':
# Then if it is, the first character determines which coordinate it is.
# If it's an X, Y, or Z we'll use the split(function) to break the line
# into a list of strings, which contains the number as the second
# element, then use float() to convert it to a real number, and store it
# in the appropriate variable.
................if Line[0]=='X':
....................X=float(Line.split()[1])
................elif Line[0]=='Y':
....................Y=float(Line.split()[1])
................elif Line[0]=='Z':
....................Z=float(Line.split()[1])
# Once we have parsed each line in the file to pull out the coordinates
# lets use some formatted printing to output the file name and values.
# Remember that "File" contains the name of the file with the extension
# so the split using the dot will give a list ['filename','extension']
# so the base name is element 1 or [0]
# Close the file and your on your way.
........print '%s %.3lf %.3lf %.3lf'%(File.split('.')[0],X,Y,Z)
........OppFile.close()

so in the end after removing the modules that were only temporary, the final program looks like

import sys,glob

def usage():
....'''
....Explanation text to print if anything is entered as an argument.
....'''
....print '\nUsage: python opp2txt.py\nThere are no arguments, it will just look in the current directory\n for files with a .opp extension, strip the coordinates out of them\n and output them to the screen\n\nListings can be redirected into a file.\n eg: python opp2txt.py > photos.txt'
....sys.exit(1)

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:
........OppFile=open(File,'r')
........FileData=OppFile.readlines()
........for Line in FileData:
............if Line[1]=='l':
................if Line[0]=='X':
....................X=float(Line.split()[1])
................elif Line[0]=='Y':
....................Y=float(Line.split()[1])
................elif Line[0]=='Z':
....................Z=float(Line.split()[1])
........print '%s %.3lf %.3lf %.3lf'%(File.split('.')[0],X,Y,Z)
........OppFile.close()
else: usage()

Now this just prints to the screen and would have to be redirected to a text file. For the fun of it the first modification once we know it is running would be to send it directly to a file. As always, final working code is found on my personal VrPython website.


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.



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.



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.

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.