Tagging and Gluing
Updated 05 Apr 2018
|
Upper levels: - QuArK Information Base - 3. Advanced customization - 3.6. Plug-Ins - 3.6.1. Plugin Tutorial |
3.6.1.3. Tagging and Gluing |
[ | - - ]
Now we get some commands to actually do something. What we want the user to be able to do is first `tag' a selected side, then later select one or more sides, and finally give another command `glue to tagged', which will move the selected sides so that they are coplanar with the tagged one. So we will actually need two (eventually three) commands in all. |
Index |
Tagging |
tiglari - 05 Apr 2018 | [ Top ] |
For tagging, What we need to do is:
The last step involves some subleties, so we'll leave it out for now. When you start editing a map with QuArK, it creates an instance of the class mapeditor (defined in quarkpy.mapeditor), which then looks after the editing of that map. Since we want to keep track of what's been tagged in this particular editing session, but not outside of it, a good way to store the info of what's been tagged is in some sort of object attached to the mapeditor. For this we'll use a Python `class' (you might want to look at the Python tutorial on classes), and we'll start by defining it like this: class Tagging: tagged = None Now the new TagSideClick command will look like this; I've dumped in excessive comments that you'd presumably want to leave out of your version. And absolutely do not worry about how much of it doesn't make sense at first. def tagSideClick (m): editor = mapeditor() # # do we have an editor? the function mapeditor() returns the editor # from which the command was invoked. If we don't, bail. # if editor is None: return # # attach a new tagging object to it (to keep track of what side # is tagged). `Tagging()' is the `default constructor', automatically # provided to make a new instance of the class, if no explicit # constructor has been defined # editor.tagging = Tagging() # # get the editor's selection-list and stick it in a local variable # tagged = editor.layout.explorer.sellist # # now check that what's selected is exactly one side # if (len(tagged) < 1): quarkx.msgbox("No selection", MT_ERROR, MB_OK) elif (len(tagged) > 1): quarkx.msgbox("Only one selection allowed", MT_ERROR, MB_OK) elif (tagged[0].type!= ":f"): quarkx.msgbox("The selected object is not a face", MT_ERROR, MB_OK) # # and at last we're ready to rock and roll! # else: # # This actually stashes our tagged side (the first & only # element of a one-element list) in the tagging object # that we've attached to the editor. # editor.tagging.tagged = tagged[0] When you test this, what ought to happen is that when you give the Tag side command after selecting a single face, there is no visible effect, but if you've violated any of the conditions, you ought to get an error-notifying message-box. So make a point of testing all the various erroneous conditions! In general, it is very important to test that an operation will make sense before performing it (in jargon, that `it's preconditions are met'), but in fact this is a rather primitive approach, and we will soom move on to menu item enabling and disabling instead. |
Glueing |
tiglari - 05 Apr 2018 | [ Top ] |
Now on to implement the gluing command! First, add a new command to the commands menu called `&Glue to Tagged', which invokes the function glueSideClick. Now to define this function. It basically has to do three things:
There's another, somewhat philsophical point about UI design, which is that it's a good idea to have some separation between the code implementing the UI and the code implementing the actual operations that the UI is being used to control, since one often wants to re-use the operations code elsewhere in the program. It's often hard to figure out exactly how to split up the function, but it's usually worth trying. So here's the UI portion: After getting the editor, we get the tagged face and the editor, check that various preconditions are met, and then call the glueing function we're going to define shortly: def glueSideClick (m): editor = mapeditor() if editor is None: return tagged = editor.tagging.tagged # # get the selection (which will be a list) # sides = editor.layout.explorer.sellist # # check that it meets conditions (this could be done more slickly) # if len(sides) < 1: quarkx.msgbox("Something must be selected for glueing", MT_WARNING, MB_OK) return if len(sides) > 1: quarkx.msgbox("Only one thing may be selected for glueing", MT_WARNING, MB_OK) return if sides[0].type != ":f": quarkx.msgbox("The selection must be a face", MT_WARNING, MB_OK) # # Now derive the new side # side = sides[0] newside = glueToTagged(side, tagged) So here's how to use the undo module: # First create an `undo' object with the quarkx.action() # function. This will keep track of the actions performed. # undo = quarkx.action() # # Now substitute the new side for the old one (in the undo object) # undo.exchange(side, newside) # # and perform the action for real in the map # undo.ok(editor.Root, "glue to tagged") Next we want to define the function that produces the new face. This involves some map object manipulation and 3D math (see 'Attributes of Internal objects of class "Face"'): def glueToTagged(side, tagged): # # Make a copy of the original (so that texture etc. info is preserved) # new = side.copy() # # .distortion rotates the face into a new position; we have # to make sure that the normal is going to point outward from # its parent poly. # if new.normal*tagged.normal < 0: new.distortion(-tagged.normal, new.origin) else: new.distortion(tagged.normal, new.origin) # # Now shift our new side into position: # new.translate(tagged.origin-new.origin) return new Later we'll see how to fix this problem by having the menu item `disabled' until a side is actually tagged, but since the problem of accessing attributes that haven't been defined yet arises elsewere, we'll show how to do it by using a function that contains an `exception handler': def gettagged(o): try: return o.tagging.tagged except (AttributeError): return None The `try' command runs the following block of code normally, except that if the kind of error named in the following `except' statement occurs, that execution is aborted, and the code in the except-block is executed instead. So this little function returns None rather than just causing an error, in case whatever is fed to it lacks a `tagging.tagged'-value. So we can prevent the grotty-looking console outburst by replacing line 4 of GlueSideClick with: tagged = gettagged(editor) if tagged is None: quarkx.msgbox("Something must be tagged",MT_WARNING,MB_OK) By now you might be wondering about the faces sometimes being called `sides', this happened because `side' is the term I first used when writing the plugin, and by the time I decided it was a mistake, the bad decision had entrenched itself with too many entanglements. Returning to the mere functioning of our plugin, another problem is that when we do tag a side, we see no indication of what we have done, in unfavorable contrast to actual QuArK, where the tagged side gets outlined in red, with a red square in the center. And also the interface invites us to perform actions that then produce error messages, such as trying to glue when there's nothing to glue to, rather than indicating at any moment what the sensible things to do are, and also there are no hints or help. Finally, the whole thing is rather clunky, we have to select faces, and then go to the command menu to tag and glue them, and wouldn't it be better if all this could be done off the right mouse-button menu? In the next three lessons we attend to these matters. |
Troubleshooting |
tiglari - 05 Apr 2018 | [ Top ] |
When things start getting a bit complicated, bugs get likely. Plugins starting with map- get loaded when you start the map editor, and if they are syntactically ill-formed, the loading process will stop, leaving you with an error message on the `console' (dark screen, red letters). After fixing the mistake, it is often possible to procede just by closing the grey window you also get (failed mapeditor window), and opening the map editor again. Sometimes this doesn't work, and you have to restart QuArK. QuArK is also supposed to show the console when a runtime error is encountered, but for some reason, this isn't always working now, and command execution just stops. You can add `debug' statements to code, which print things out to the console, and you can also put QuArK into `developer mode' in the options menu. Then `squawk' statements will print out messageboxes with whatever message you want. e.g. squawk('newface: '+newface.shortname) Finally, in developer mode, a `Reload' command will appear on the command menu; this allows you to reload a module. This usually works for plugins (tho not always for quarkpy files), with some glitches, such as duplication, etc., of menu items. But it's often quicker than restarting QuArK from scarch. It can't be made perfect, because Python doesn't have any `unload' command that could reverse the effects of loading a module. A tested version of what you ought to have now is maptagside1.py in plugin_examples.zip |
Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/ |
[ Top - ] | -