## 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=='':
..layer=105
else: layer=string.atoi(input)

for EntNum in range (Ws.GetLineCount(WsNum)):
..if layer==(Ws.GetLineLayer(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=='':
..layer=105
else: layer=string.atoi(input)
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.

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.