Monday, May 18, 2009

We all have our limits

Note that I am trying a new method of posting code, hope it works better but of course the original code itself can always be downloaded from the website where it is stored.

Quite often when writing some sort of import function I would like to limit the data imported to certain physical constraints. I started out asking the user if they want to only act on coordinate values that fall within a bounding box, then if they said yes, prompt them to ID the bounding line. This works great as far as it goes, as part of the ID function I would set a flag (in this case BoundrySet) to 1 then later in the program if BoundrySet was true the IsPointInside method of the Line class could be called. Something like....

if BoundrySet :
if BoundingLine.IsPointInside (X,Y): Do something


But then I got thinking, there are several easy ways to check for bounding areas without having a line already digitized. How about using the screen boundaries, or digitizing a rectangle based on the lower left and upper right corners, or for that matter just start digitizing points and create a line in memory on the fly. Well it turns out that I already had the snippets I needed in other functions so just copied them out, cleaned them up a bit (and I do mean a bit, it still isn't particularly pretty, but it works) and created a basic function that I can paste into any program where spatial filtering is helpful.

There are a couple of caveats. Using the rectangle select mode requires that you select the lower left point first, then the upper right based on the screen orientation. I'll probably clean this up some day and allow for more flexibility but it falls into the "quick hack for some purpose, and I know how it acts" category of code that gets the job done but isn't as attractive as it should be. Using the "Digitize" mode allows the user to start clicking on positions and builds the boundary line on the fly, if it would be helpful it would be just as easy to record the line for future interaction but for now I just throw it on the screen. The nice thing about this dialog is that it behaves just the way you expect so if the focus is in the drop down and you hit an 'r' rectangle mode will be activated and so on.

print 'SetBoundary.py modified 8:00 AM 5/15/2009'
# Set spatial bounding for processing
'''
Copyright 2009 Dennis Shimer, M.A.N. Mapping Services Inc.
No warranties as to safety or usability expressed or implied.
Free to use, copy, modify, distribute with author credit.

Really just a container for the code snippet that would do spatial
filtering as part of a larger program. Just written as working code
here for testing purposes.

Variables of interest:
None
'''
import math

BoundingLine=PyVrLine()
Gr=PyVrGr ()
Gui=PyVrGui()
BoundrySet=0

PromBox = VrPromBox ("Set Boundry", 30, 1)
PromBox.AddCombo ('Boundry', 5, 0, 0)
PromBox.AddComboItem ('Line')
PromBox.AddComboItem ('Screen')
PromBox.AddComboItem ('Rectangle')
PromBox.AddComboItem ('Digitize Points')
PromBox.AddComboItem ('None (whole file)')
PromBox.SetFocus()
if PromBox.Display(1)==0:
BoundryType=PromBox.GetComboByPrompt ('Boundry')
if BoundryType==0:
BoundingLine.Id()
BoundrySet=1
elif BoundryType==1:
for corner in range(4):
x,y,z=Gr.GetWinCorner (corner,0)
BoundingLine.AddPoint(x,y,z)
BoundrySet=1
elif BoundryType==2:
Stat,llx,lly,z=Gui.GetCoord('Boundry')
Gr.DrawMode (1,-1)
Gr.PenNum (1)
Gr.DrawMarker (llx,lly,z,MARK_CROSS,Gr.GetWinScale ()*.2,-1)
Stat,urx,ury,z=Gui.GetCoord('Boundry')
Gr.EraseMarker ()
ScreenAngle=Gr.GetWinRot()[2]
Angle=math.atan((ury-lly)/(urx-llx))-Gr.GetWinRot()[2]
Diagonal=PyVrGeom().Dist(llx,lly,urx,ury)
Height=math.sin(Angle)*Diagonal
if Height < 0.0: Height=Height*-1.0
Width=math.cos(Angle)*Diagonal
if Width < 0.0: Width=Width*-1.0
BoundingLine.AddPoint(llx,lly,z)
lrx=llx+(Width*math.cos(ScreenAngle))
lry=lly+(Width*math.sin(ScreenAngle))
BoundingLine.AddPoint(lrx,lry,z)
urx=lrx-(Height*math.sin(ScreenAngle))
ury=lry+(Height*math.cos(ScreenAngle))
BoundingLine.AddPoint(urx,ury,z)
ulx=urx-(Width*math.cos(ScreenAngle))
uly=ury-(Width*math.sin(ScreenAngle))
BoundingLine.AddPoint(ulx,uly,z)
BoundingLine.AddPoint(llx,lly,z)
BoundingLine.Plot()
BoundrySet=1
elif BoundryType==3:
Stat=0
while Stat==0:
Stat,x,y,z=PyVrGui().GetCoord('Dig Boundry')
BoundingLine.AddPoint (x,y,0.0)
BoundingLine.Plot()
BoundingLine.Close(2)
BoundingLine.Plot()
BoundrySet=1


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.

Thursday, November 01, 2007

Inherit everything but the kitchen sink.

Back when I was just a young map maker, I was actually fairly sharp, and had a memory that was quite useful. I worked on a CAD system that until VrOne came along seemed like one of the fastest, most practical, easy to use map collection systems around. It used a numeric command set that allowed you to set any combination of insert parameters to a 3 digit code. Alas you were limited to 120 codes which seemed like plenty and were quickly memorized. Of course now I can't remember what I had for breakfast (I did have breakfast didn't I), but I can remember all 120 of those codes. The flexibility of Vr allowed me to easily make macros that do those 120 tasks, but over the years we continued to add things. Now I know there is a function key that will put in a double yellow road centerline, but what combination of letters did we use for the function key (there are 26 letters and 5 unique words in that description). Sometimes when I'm collecting I think good grief, there is what I want right there, it sure would be nice to just change my current parameters to match that existing entity. Well, back in January I added a quick command that came in handy more often than I thought and has grown up since then. The premise was simple, a command that allowed you to Id a line, then start digitizing using the attributes of that line. Remember it looked something like this

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))

It was so handy that I made one that did the same thing for fly-lines, and symbols. This way when I'm tying to an edge or come across something I forgot, and can't remember the function key, all I need is the short mnemonics for my inhlin, inhfly, and inhsym commands. Then I got thinking, what if I'm already digitizing a line or symbol and want to match something I happen upon? Well here is my inhent.py which some folks might find a bit odd but it also gives examples of a couple of useful Vr Objects

print 'inhent.py Inherit From Entity modified 8:52 AM 10/31/2007'
'''
Copyright 2007 Dennis Shimer, M.A.N. Mapping Services Inc.
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.

Asks user to identify an entity, then sets certain
parameters inherited from the identified entity.

'''

Line=PyVrLine()
Sym=PyVrSym()
Text=PyVrText()
Gui=PyVrGui()

Ws=PyVrWs()
# There are a couple of times we'll need to multiply by the scale because ground
# units are returned, and we'll need to set based on map units.
Scale=PyVrWs().GetTargetScaleIn(Ws.Aws())

EntWs,EntNum,EntType,LinePtNum,x,y,z=Ws.IdEnt(-1,-1,-1,-1)
# Man I love IdEnt, it isn't in the documentation yet but by using
# -1 in the SeaEnttype spot, it remembers the last state.
if EntType==1:
# If it's a line grab the appropriate settings
...Line.Load(EntWs, EntNum)
...Layer,Graphic,Width=Line.GetLayer(),Line.GetGpoint(),Line.GetWidth()
# This line is left over from when I forced the user to hit end after
# identifying the entity, which was useful if you grabbed the wrong one
# but I found I liked it this way better, so the following line doesn't
# really matter because it just does it after you id something.
...Gui.DspMsg0 ('lay=%d, grp=%d, wid=%d'%(Layer,Graphic,Width))
...Type='Line'
if EntType==3:
# Ditto if it's a symbol.
...Sym.Load(EntWs, EntNum)
...Layer,Graphic,Radius,Rotation=Sym.GetLayer(),Sym.GetGpoint(),(Sym.GetRad()/Scale),Sym.GetRot()
...Gui.DspMsg0 ('lay=%d, grp=%d,rad=%.3lf,rot=%.3lf'%(Layer,Graphic,Radius,Rotation))
...Type='Symbol'
if EntType==4:
# or even a piece of text.
...Text.Load(EntWs, EntNum)
...Layer,Font,Height,Width,Rotation,Label=Text.GetLayer(),Text.GetFontNum(),(Text.GetHgt()/Scale),(Text.GetWdt()/Scale),Text.GetRot(),Text.GetText()
...Gui.DspMsg0('lay=%d,fnt=%d,hgt=%.3lf,wdt=%.3lf,rot=%.3lf,txt=%s'%(Layer,Font,Height,Width,Rotation,Label))
...Type='Text'

# Once you grab the settings desired, the only thing left to do is
# push the proper key-ins.
if Type=='Line':
...Gui.PushKeyin ('lay=%d,grp=%d,wid=%d'%(Layer,Graphic,Width))
elif Type=='Symbol':
...Gui.PushKeyin ('lay=%d,grp=%d,rad=%.3lf,rot=%.3lf'%(Layer,Graphic,Radius,Rotation))
elif Type=='Text':
...Gui.PushKeyin ('lay=%d,fnt=%d,hgt=%.3lf,wdt=%.3lf,rot=%.3lf,txt=%s'%(Layer,Font,Height,Width,Rotation,Label))

Given that there probably aren't 3 people in the world that read this on a regular basis, I'll probably hit it about once a month from now on. Don't forget to contribute to the forum.

Happy coding.

Tuesday, August 07, 2007

Time out

It is that time of year when I begin to pursue my second job as middle school soccer coach, and high school referee (and parent spectator). Because of this, I'm not even going to think about posting until late October.

Thursday, July 19, 2007

Finally A Vr Command

Well enough playing around with external files. It's about time to head back to what VrPython was intended for and write a new function. This one in particular allows the user to parallel part of an existing line. This is a good example of how a program evolves (to say it was intelligently designed probably infers too much), because as it started off the user had to digitize the offset distance. Then with the advent of VrArgs in 3.3.? it seemed like a good idea to put in the option to enter an offset as a argument after the user typed parpart. Of course with the opportunity to put in a argument as an offset, it seemed silly to not have a dialog somewhere. The problem was that I didn't want dialog every time, and you already had to Id the line, start point, end point, and offset which was approaching the limits of how much I wanted the user to have to do. Hence the decision that if you got all the way to the point of digitizing the offset and hadn't specified one but hit an "end" or "#" without doing so the program would bring up a dialog to allow typing one in. Well let's take a look

#This has become the standard I use to start each program.
print 'parprt.py ParallelPartial modified 8:23 AM 7/13/2007'

'''

Copyright 2007 Dennis Shimer, M.A.N. Mapping Services Inc.

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.


Inserts a line parallel to the identified line.
Allows for picking start and
end points of new line, and offset.
If a number is passed it as the first
it will be used as an offset.
If the <#> End button is pressed during offset
measurement a
dialog will come up prompting for offset.

'''

Ws=PyVrWs()

Line=PyVrLine()

Gui=PyVrGui()

WsNum=Ws.Aws()
# Create all the objects we will use.

while Line.Id() != -1:
#
In theory you could partially parallel many lines
# though in reality it doesn't happen much, but will do so
# until the user doesn't Id a line which returns -1.

....Ws.UndoBegin(WsNum,"inspar_part")
# The undo is inside the Id loop so each line paralleled
# could be undone individually.


....Gui.DspMsg0('Dig start of new line')
# Since GetCoord() isn't very customizable as far as buttons
# and messages, we'll display instructions in the message area.

....Stat1,NewX1,NewY1,NewZ1=Gui.GetCoord('Point1')
# And get a coordinate for where the newly paralleled line
# Should begin.


....Gui.DspMsg0('Dig end of new line')
....Stat2,NewX2,NewY2,NewZ2=Gui.GetCoord('Point2')
# And the same on the other end.

....if VrArgs:

........print VrArgs

........Offset=float(VrArgs[0])
# This whole if statement was added later after the introduction
# of VrArgs. If a string (or multiple strings) are placed after
# the script name, they are passed in as a list of strings. So the
# test would fail if VrArgs was empty (nothing passed) and would
# pass to the else statement. If however something was passed,
# convert it to a real number and assign it to the offset. Bonus
# points for anyone who identifies the huge bug I just noticed in
# this 3 line block.


....else:
# In other words if VrArgs is not true (is empty).

........Gui.DspMsg0('Dig offset # End for dialog')
........StatS,SnapX,SnapY,SnapZ=Gui.GetCoord('Offset')
# Same as above, display instructions and read a coordinate
# that represents how far the cursor is from the line.


........Offset=-1.0*(Line.SnapPoint (SnapX, SnapY)[3])

# Use the computed offset. The -1 is just to correct for
# how SnapPoint computes the distance.

........if StatS:
# This comes about if the user hits the end button instead
# of a 1 to cancel the digitize offset.

............PromBox=VrPromBox('Prompt title',20,1)
............PromBox.AddDouble('Enter a real number',.5,2)
............if (PromBox.Display(0) == 0):
................Offset=PromBox.GetDouble(0)

# Just a simple prombox to allow for entering the offset if
# it wasn't passed or computed in any other way.

....Num1=Line.SnapPointAdd (NewX1,NewY1,0.0)
....Num2=Line.SnapPointAdd (NewX2,NewY2,0.0)

# We are adding points to the line object that was identified
# this is ok because later we will Rec() instead of ReRec() which
# will create a new line.

....if Num1>Num2:
........tmp=Num1

........Num1=Num2

........Num2=tmp
# At one point DelPoints didn't work properly if the beginning
# point was higher than the end point (it may not still, but doesn't
# really matter if it is checked and swapped if necessary.

....Line.DelPoints (Num1, Num2, 1)
# Mode 1 in effect deletes points outside begin and end rather
# than between them (which would be mode 0)

....Line.Offset(Offset)
....Line.Rec(WsNum)

....Line.Plot()

# Take that line object, offset it by the distance entered or
# computed, record it as a new entity, plot it and there you go.
# Part of the original line has been stored as a new line parallel
# to it.
....Ws.UndoEnd(WsNum)

Reminder that as with all other scripts, you can either copy and paste what you see here (substituting your prefered indentation for the holding characters "....", or find the original at http://python4vr.googlepages.com

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.