Tuesday, July 10, 2007

Life Outside Vr Wrap Up (almost)

Before going back to writing functions for Vr let's wrap up a couple of things. First binary files, I have recently begun to like the idea of persistent parameters for several of my programs. I really need to create some basic generic parameter code to paste in. In the end I'll probably settle on ascii files with keywords, or at least comments. But what if you want to use binary, or what if you want to read a binary file that you know (or could guess) the structure of. An example is the old .COO coordinate files used by orientp3 in pre-Vr days. We still use it and often want to work directly with the .COO files for various things. But they are binary, here are some basics working with something similar to the parameter file we used before. This will be plain python, you can copy and paste this, download testbinary.py from my source code website, then either run it from a command line like
python testbinary.py
or open or paste it into pyedi and run it inside Vr. The only requirement is that you will need to have python installed because it uses two standard modules. By this time I am going to assume some python knowledge and ability to read the code. I am thrilled to answer any questions that come up, but I will post this code in a very plain, line at a time manner rather than using multiple methods on a single line. I'm also going to use my favorite method of accessing binary data since as is common in python there are probably dozens of ways of doing it. You could probably do this in about 8 lines but the point is to explain what is going on. I'll also assume that anyone interested in this would take the time to look up the modules listed to see how many cool things they will do... So here goes

If you were to run this script 3 consecutive times it would give 3 different responses. First that the parameter file was created but without any values read. Second it would read the default values, tell you what the values are and modify them. Third it will read the modified values print them out and tell you that it found modified values and not the defaults.

import os,struct
# We'll use os to test for the existence of the file, an struct to
# interact with the binary data.

FormatString='10sddi'
'''
The format string tells several parts of the program what we are working
with in regards to binary data. The format string listed above represents
a 10 character string, 2 double precision real numbers, and an integer.
Same as before but I added the int to show by example. Note that it is
just a string with letters representing different kinds of data.
'''

ParFileName='testbinary.par'
# The file by this name will show up in the directory the script is run in.

print 'Bytes occupied by binary data',struct.calcsize(FormatString),'\n\n'
'''
Sometimes you need to know how many bytes to read from a file in order
to get one set of data when multiple sets exist, a good example goes
back to the coordinate file mentioned earlier. In order to read and process
one point at a time you can only read the necessary bytes. the calcsize
method in struct will read a format string and tell how many bytes are
represented. You can go into a python interpreter, import struct then
evaluate some single item format strings like the following for a
float, a double, an int, and a 1 character string.
>>> struct.calcsize('f')
4
>>> struct.calcsize('d')
8
>>> struct.calcsize('i')
4
>>> struct.calcsize('s')
1
'''

if not os.path.isfile(ParFileName): # If the file doesn't exist.
...ParFile=open(ParFileName,'wb')
...# Then open it in write (create or erase) mode with a binary modifier

...BinaryData=struct.pack(FormatString,'Word',.2,.0,0)
...'''
...The pack method in the struct module uses the format string to process
...the data that comes next into the bytes which represent them in binary
...form. It is important that the amount and type of data following the
...format string match, notice I am passing in a string (less than 10
...characters allowed, 2 real numbers and an integer.
...'''

...ParFile.write(BinaryData)
...ParFile.close()
...# We have a binary file and binary data so just and write and close it.

...print 'Program run with no existing file'
...print 'Data set to defaults.'
...print 'testbinary.par should now exist.'
else:
...ParFile=open(ParFileName,'r+b')
...# If it does exist open for reading + writing with binary modifier.

...BinaryData=ParFile.read()
...'''
...This time since the file exists we are going to get the binary data
...right out of the file with a read. Since we want the whole file there
...is no reason to use calcsize to tell it how many bytes to read.
...'''

...DataList=struct.unpack(FormatString,BinaryData)
...# Going the other direction, lets use unpack to use the format string
...# to convert the binary data into a list of values.

...print 'Data from file, note it is a list\n ',DataList
...# and print the list.

...ParFile.seek(0,0)
...'''
...Now assuming that there are new values that need to be used to update
...the parameter file. The first thing we need to do is go back to the
...beginning of the file, read up on seek, but this basically moves you
...0 bytes from the beginning. If in the coordinate file example I had a
...data structure that require 24 bytes and wanted to get the 3rd value
...from the file I could to the 48th byte and read 24. This way you can
...read, write, and overwrite data in place within a file.
...'''

...print 'File position after seek ',ParFile.tell()
...# Every open file has a tell method which gives the current position.

...BinaryData=struct.pack(FormatString,'Updated',1,2,3)
...ParFile.write(BinaryData)
...# Same idea as above, just using the changed data (wherever it came from)

...if DataList[0].split('\x00')[0]=='Updated':
......print 'You have seen it all.'
......print 'Remove testbinary.par to start over'
......'''
......There may be a better way to convert this 10 character string to
......what you normally think of as a string, but it comes back padded
......with the 0 bytes and this works simply enough. Think of it this
......way 'Updated\x00\x00\x00' has 3 extra bytes, if you split on that
......character you get back ['Updated', '', '', ''] of which 'Updated'
......is the first or [0] element. Ugly but I've never really look into
......a better way.
......'''

...else:
......print 'Program run with an existing file'
......print 'Data was updated run again to see change'
......# 2nd run string doesn't equal 'Updated' so print appropriate message.

...ParFile.close()
...# Good form to explicitely close files.

I need to wrap it up for now, I'll have to save the parsing command for next time.

No comments:

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.