Scripting - or LOD help |
|
|
| Posted: 09 June 2010 11:57 AM |
|
|
|
Jr. Member
Total Posts: 35
Joined 2009-03-17
|
I’ve got to thin out a bunch of QUAD polys that are being used in large hedgerow system. The issue I’m having with the LOD generator, is it breaks down quads into tris for removal.
I was wondering if there is a script / or a way to select every other face within a node with a script, or every third face, ect… that way i can just delete the faces as a quad.
Just curious. - Anyone up to the task?
|
|
|
|
|
|
| Posted: 09 June 2010 03:30 PM |
[ # 1 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
Interesting. Fairly simple, except I can’t ever seem to get a global counter to work from mgWalkDb. It reverts to the initialized value when you return to the function. I chunked up a baseline source - maybe Chris can tweak it to continually count - something I’d like to do in other instances. the following, if counter worked, would mgWalk and retain the first of four polys, and delete the rest. UberCare must be talken, it is indiscriminate about node boundaries. you better have quads!
def delete_polys ( db, parent, rec , counter): counter = counter + 1 if (counter > 4): counter = 0 type = mgGetCode (rec) if ( type == fltPolygon ): if ( counter == 1 ): print "keep the first %d" % counter else: print "delete second, third, and fourth %d" % counter mgDetach ( rec ) mgDelete ( rec ) return MG_TRUE
files = mgPromptDialogFile ( None, MPFM_OPEN, MPFA_FLAGS, MPFF_FILEMUSTEXIST|MPFF_MULTISELECT, \ MPFA_PATTERN, "OpenFlight Files|*.flt", \ MPFA_TITLE, "OpenFlight File" ) status = files[0]
if (MSTAT_ISOK(status)): numFiles = files[1] fileNames = files[2:len(files)] counter = 0 for fileName in fileNames: print "opening file" print fileName db = mgOpenDb (fileName) mgWalk ( db, delete_polys, None, counter, MWALK_VERTEX|MWALK_MATRIXSTACK) mgWriteDb (db) mgCloseDb (db) print "Finished, Finito, and done"
|
|
|
|
|
|
| Posted: 09 June 2010 04:00 PM |
[ # 2 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
BTW - Lars - I also wrote a script that will allow you to browse to your “resource directory”, select as many OpenFlight files fromthere as you want, and write the XML Style File (.smf) that CTS uses to do the point feature projection - including the model name! No mor hand editing style files with projection methods for me! timely, since I am developing laydowns using thousands of instances of hundreds of buildings.
I still haven’t heard back fom management regarding my “Top 5 reasons I still need CTS”.
|
|
|
|
|
|
| Posted: 09 June 2010 07:47 PM |
[ # 3 ]
|
|
|
Moderator
Total Posts: 603
Joined 2008-07-02
|
except I can’t ever seem to get a global counter to work from mgWalkDb
I found the following at:
http://www.penzilla.net/tutorials/python/functions
Python passes all arguments using “pass by reference”. However, numerical values and Strings are all immutable. You cannot change the value of a passed in immutable and see that value change in the caller. Dictionaries and Lists on the other hand are mutable, and changes made to them by a called function will be preserved when the function returns. This behavior is confusing and can lead to common mistakes where lists are accidentally modified when they shouldn’t be. However, there are many reasons for this behavior, such as saving memory when dealing with large sets.
This both explains the problem and suggests strategies for solving it. In your case, counter is numeric, hence its value is immutable, hence the changes you make don’t make it back to the caller. The trick is to pass something that is NOT immutable, like a dictionary, list or class object.
Below is a simple sample showing how to pass a counter (inside a class) to mgWalk and how mgWalk, in turn, would update the counter hence making the changes visible to the caller. This sample walks the current database and counts all the polygons it finds. It then prints out that number.
# declare a class to "wrap" a count variable class counter_wrapper: count = 0
# this is the mgWalk callback function def count_polys (db, parent, rec, counter): type = mgGetCode (rec) # if the node is a polygon, "count" it if (type == fltPolygon): counter.count = counter.count + 1 return MG_TRUE # declare an object of our "wrapper" class counter = counter_wrapper() db = mgGetCurrentDb () # pass this object to mgWalk mgWalk (db, count_polys, None, counter, 0) print "Found",counter.count,"polys"
I have a couple of additional observations on your original script:
1) You don’t have to mgDetach before mgDelete. mgDelete detaches and deletes. The call to mgDetach is unnecessary in your function delete_polys.
2) Along those same lines, you cannot mgDelete (or mgDetach) nodes inside the mgWalk callback. In fact, you should NOT change the hierarchy in any way during a walk. This completely changes the behavior of the walk itself. As written (if your counter worked), your call to mgWalk would terminate immediately after the first polygon that gets deleted. To fix this, you need a way for your walk callback function to “collect” the nodes that will be deleted after the walk is done. I have included another sample at the bottom of this thread showing how you might do this.
3) In delete_polys, I believe you want to increment and modulo the counter only if the node is a polygon (move that code inside the “if ( type == fltPolygon ):” block.
Here is the sample I from above modified to return a list of all the polygons it finds. You could use this strategy in your walk callback to form a list of polygons that would get deleted when the walk completes:
# declare a class to "wrap" a count variable and a list class counter_wrapper: count = 0 list = []
# this is the mgWalk callback function def count_polys (db, parent, rec, counter): type = mgGetCode (rec) if (type == fltPolygon): counter.count = counter.count + 1 counter.list.append(rec) return MG_TRUE # declare an object of our "wrapper" class counter = counter_wrapper() db = mgGetCurrentDb () # pass this object to mgWalk mgWalk (db, count_polys, None, counter, 0) print "Found",counter.count,"polys" # examine the faces in the list # here is where you would "delete" the faces if (len(counter.list) > 0): for face in counter.list: print mgGetName(face)
I leave it as an exercise for the reader to assemble all these pieces in order to fix the original posted script.
And I offer a hearty thanks to you for posting it in the first place
|
|
|
|
|
|
| Posted: 10 June 2010 09:41 AM |
[ # 4 ]
|
|
|
Jr. Member
Total Posts: 35
Joined 2009-03-17
|
<<<“doesn’t have the first clue about code / scripting”
I tried copy / pasting into the script editor / running it
it asked to open a .flt file, I pointed it to the filed then ran it - but the model never changed.
oh well - maybe just a “keep quads / no triangulation” option in the lod generator would suffice.
|
|
|
|
|
|
| Posted: 10 June 2010 10:06 AM |
[ # 5 ]
|
|
|
Moderator
Total Posts: 386
Joined 2008-07-03
|
There is one very important thing with respect to the script Kent wrote. It does not work on currently open databases. He prompts you for the db to modify using the function:
mgPromptDialogFile()
You did not see changes because the script modified the file on disk, not the db in memory. You might have seen changes if you ran the script, then opened the file. Steve’s scripts use the line:
db = mgGetCurrentDb()
This is how you can modify the currently open db. This is also the reccommended way to write your scripts. It is much easier (one line instead of several) and it will work with future improvements to scripting. The batch scripts using mgPromptDialogFile() will have to be re-written to use the db = mgGetCurrentDb() method in order to take advantage of future improvements.
|
|
|
|
|
|
| Posted: 10 June 2010 10:09 AM |
[ # 6 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
Don’t be afraid of the scripting! I highly recommend “Python For Dummies” to learn some basic things. there is a whole chapter on classes which accurately describe what Steve laid out, but I didn’t read the whole book and its at the back.
If you used the script as I posted it, it does nothing because the counter never increments, so is always 1 and therefore the conditional in the method (function) is always true.
I’ll see if I can’t get Steve’s class/method to work and repost.
Scripting is now a mainstay of my entire team’s workflow - I have collected gripes about certain repetitive things and written scripts to do them (collapsing matrices on 30,000 instances takes 5 minutes in Creator, 9-10 seconds via script). It’s a good way to get improved functionality NOW while waiting to see if and when Presagis may customize/enhance the tool.
|
|
|
|
|
|
| Posted: 10 June 2010 10:16 AM |
[ # 7 ]
|
|
|
Moderator
Total Posts: 386
Joined 2008-07-03
|
Kent, we would love to have a peek into your library of scripts! It could give us valuable insight into how to improve Creator. Even a list of scripts you wrote and a description of what they did would be helpful.
|
|
|
|
|
|
| Posted: 10 June 2010 10:59 AM |
[ # 8 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
I am working on a documentation set as we speak for use on my team. I can certainly give you insight as to what is in my library, I’ll have to pass “release” through a higher power.
here’s a taste:
Collapse_Matrices
walks a DB and collapses all matrices on external references
Move matrix
walks a DB and takes all groups with XForms and Xref children and pushes the matrix to the Xref. Develpped because Place Model results in different hierarchy than External Reference ....
I have an interesting perl script / creator script combo that takes a shapefile, converts to a readable text file, then imports that text file into a huge master reference OpenFlight file, then splits it up along girds of 10 minutes on a side. I do this for multiple feature layers (trees, powerlines, buildings), then run an assembly script which goes out and finds all the grids that have data, creates a master file in Flat earth with a local refernce origin in the center, then pulls in all of the grid layers and reprojects them into local space. Great for setting up a gridded world of master files from a large area database full of vector feature data 8*).
And of course, “write_style_file.py” which allows you to select all or any files in a directoy, and creates a CTS style file with point feature projection methods set for each one including MY shapefile standard field names. Certainly takes the tedium out of that process!
|
|
|
|
|
|
| Posted: 10 June 2010 11:31 AM |
[ # 9 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
Works now - good wrapper! Also - I had the count in the call but outsid the if poly conditional, so it was counting all the nodes, not just the polys. But since the counter didn’t work, I didn’t notice it!
On the open database, use this:
class counter_wrapper: count = 0
def delete_polys ( db, parent, rec , counter): type = mgGetCode (rec) if ( type == fltPolygon ): counter.count = counter.count + 1 if (counter.count >= 4): counter.count = 0 if ( counter.count == 1 ): print "keep the first %d" % counter.count else: print "delete second, third, and fourth %d" % counter.count mgDelete ( rec ) return MG_TRUE
db = mgGetCurrentDb () mgWalk ( db, delete_polys, None, counter, MWALK_VERTEX|MWALK_MATRIXSTACK)
print "Finished, Finito, and done"
do the same thing to all:
class counter_wrapper: count = 0
def delete_polys ( db, parent, rec , counter): type = mgGetCode (rec) if ( type == fltPolygon ): counter.count = counter.count + 1 if (counter.count >= 4): counter.count = 0 if ( counter.count == 1 ): print "keep the first %d" % counter.count else: print "delete second, third, and fourth %d" % counter.count mgDelete ( rec ) return MG_TRUE
files = mgPromptDialogFile ( None, MPFM_OPEN, MPFA_FLAGS, MPFF_FILEMUSTEXIST|MPFF_MULTISELECT, \ MPFA_PATTERN, "OpenFlight Files|*.flt", \ MPFA_DIRECTORY, "c:/StarTeamWorld/MSAT/29P_R200s/AOI/flight", \ MPFA_TITLE, "OpenFlight File" ) status = files[0]
if (MSTAT_ISOK(status)): numFiles = files[1] fileNames = files[2:len(files)] counter = counter_wrapper() for fileName in fileNames: print "opening file" print fileName db = mgOpenDb (fileName) mgWalk ( db, delete_polys, None, counter, MWALK_VERTEX|MWALK_MATRIXSTACK) mgWriteDb (db) mgCloseDb (db) print "Finished, Finito, and done" If you want to select multiple files and
now how about one that takes a “good” node, creates a switch parent, loads a “damaged” version of the texture pattern, creates a copy, calls it “bad”, and recodes the polygons’ texture patterns to look at the damaged patern. Voila - automatic good/damaged version model builder!
|
|
|
|
|
|
| Posted: 10 June 2010 11:39 AM |
[ # 10 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
Obviously - you can tune the counter do do every second or third instead of fourth .....
|
|
|
|
|
|
| Posted: 10 June 2010 11:51 AM |
[ # 11 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
With the robust capabilities inherent in the scripting, and the obvious potential for user sharing, have you given thought to a dedicated scripting section for the forum?
I think it would be useful for experienced users to interact and novice scripters to get more comfortable in a “safe” environment.
DON’T FEAR THE SCRIPTER! (give me more COWBELL!)
|
|
|
|
|
|
| Posted: 10 June 2010 12:03 PM |
[ # 12 ]
|
|
|
Moderator
Total Posts: 603
Joined 2008-07-02
|
Kent,
Kudos to you, first, for contributing such good stuff to this forum. You really help to make this work!
Second, I have some suggestions for your script.
1) In the first script (that works on the current database), you need to declare counter before calling mgWalk:
counter = counter_wrapper()
2) In both scripts (as I mentioned in my previous post), you cannot mgDelete nodes in walk callbacks. In fact you should not “change” the hierarchy in any way while you walk or the walk may not work right. In this case, you will find that the walk will stop working correctly after deleting the first node. After you delete the node, the walk won’t know how to go to the “next” node so only one node per parent will get deleted. Instead, you will need to “collect” the nodes you want to delete in a list in your walk callback, then when the mgWalk returns, “process” the faces in the list (see updated script below)
3) Not fatal but you don’t need MWALK_VERTEX or MWALK_MATRIXSTACK in your walk flags in this case. It won’t hurt if you leave these flags in - but they will make your script run slightly slower. In this context you only need to walk down to face level, not to vertices so if you leave MWALK_VERTEX off, your script will run a bit faster. Also, you really only need MWALK_MATRIXSTACK if you plan to query vertices for their world (transformed) coordinates. Again, in this case that is not needed. Taking them out will speed up processing.
Finally, here is an updated version of the script that works on the current database. Note the following:
- In the function process_polys, the script “selects” the faces instead of deleting them. This might help you to visualize what faces the script “would” delete. This lets you tweak the script a bit before committing to the destructive deletions. When you are happy that the script “finds” the nodes you want, change mgSelectOne to mgDelete and now your script will delete the faces instead of simply selecting them. I’m afraid that won’t work in the “batch” script since selecting nodes in files that are not on the Creator desktop does not make any sense.
- I have replaced your walk flags (passed to mgWalk) with 0, this will speed the scripts up a touch
Anyway, here you go:
class counter_wrapper: count = 0 # this list "collects" the faces we want to delete list = []
def delete_polys ( db, parent, rec , counter): type = mgGetCode (rec) if ( type == fltPolygon ): counter.count = counter.count + 1 if (counter.count >= 4): counter.count = 0 if ( counter.count == 1 ): print "keep the first %d" % counter.count else: print "delete second, third, and fourth %d" % counter.count # put the face in the list (for later) # you cannot mgDelete nodes in walk callbacks!! counter.list.append (rec) return MG_TRUE
def process_polys (counter): if (len(counter.list) > 0): for face in counter.list: # change this to mgDelete (face) # after you are happy with the # faces this script "finds" mgSelectOne (face)
counter = counter_wrapper() db = mgGetCurrentDb () mgDeselectAll (db) # in this case, we can use default walk flags (0) mgWalk (db, delete_polys, None, counter, 0) # now counter.list contains the faces you want to delete # send them to this function process_polys (counter)
print "Finished, Finito, and done"
|
|
|
|
|
|
| Posted: 10 June 2010 12:07 PM |
[ # 13 ]
|
|
|
Moderator
Total Posts: 603
Joined 2008-07-02
|
With the robust capabilities inherent in the scripting, and the obvious potential for user sharing, have you given thought to a dedicated scripting section for the forum?
There is a forum devoted to OpenFlight, which currently has a subforum for the OpenFlight API. We could add another subforum for OpenFlight Script. Frankly, I’m ok with taking OpenFlight Script questions on the Creator forum but if folks would rather have a forum dedicated to Scripting, we could consider it.
Thoughts anyone?
|
|
|
|
|
|
| Posted: 10 June 2010 01:02 PM |
[ # 14 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
The script that works on the current DB probably worked in my test because I still had the scripting environment open. NOTE TO USERS: anything you define in a script is persistant across other scripts unless you close Creator and start it up again 8*).
I don’t know if a scripting section is best contained in the OpenFlight or Creator section. It is Creator Functionality built on the OpenFlight API, and usually called within Creator ......
|
|
|
|
|
|
| Posted: 10 June 2010 03:18 PM |
[ # 15 ]
|
|
|
Sr. Member
Total Posts: 550
Joined 2009-03-17
|
And I like the append to selction list, but the delete does work ...... maybe it shouldn’t, but that’s a bug I’ll take any day!
|
|
|
|
|