Life Drawing Classes are back on! yay! Here’s the one piece worth posting from this weeks class.
June 17, 2010
June 14, 2010
Drag ‘n’ Drop UI in Maya
Creating Drag/Droppable UI in Maya is a really nice way of making your UIs either more user-friendly or adding a lot more functionality without adding any more visible UI controls. It is also very easy to set up and manage once you know how.
Nearly all UI controls (of all types; buttons, sliders, fields, layouts, etc..) support being a drag zone, drop zone or both. If you add a drag callback flag to your UI control it will be dragable, if you add a drop callback it will be a drop zone. Add both callbacks and your UI control can be dragged and dropped to!
To make a piece of UI drag or droppable you simply add the appropriate flag and it’s function to the control command. For example:
button -l "Create Cube" -c "polyCube" -dgc "dragButton" -dpc "dropButton";
This creates a normal button that, when pressed creates a cube. When MMB-dragged, the button will execute the function dragButton and if anything is dropped onto the button, the function dropButton will be executed.
Every UI control uses these same two flags to support drag/drop (-dgc and -dpc) and when their functions are executed Maya automatically passes certain parameters into them.
Of course we have to write these callback functions, and every callback you write must always contain the same correct input parameters:
// the drag function: proc string[] dragProc(string $dragCtrl,int $x, int $y, int $mods) { ... } // and the drop function: proc dropProc(string $dragCtrl, string $dropCtrl, string $msgs[], int $x, int $y, int $typ) { ... }
Don’t worry about what all those params do just yet, we’ll get to that later, for now lets just create some test UI:
{ string $win = "dragDropTestWindow"; if (`window -exists $win`) deleteUI -window $win; window -title "Drag/Drop Window" $win; columnLayout -adj 1 -rs 5; button -h 50 -l "Drag Me!" -dgc "dragCallBack"; button -h 50 -l "Drop Here!" -dpc "dropCallBack"; button -h 50 -l "Drag/Drop Here!" -dgc "dragCallBack" -dpc "dropCallBack"; separator -h 20; button -h 50 -l "MMB-Click me!" -dgc "dragCallBack" -dpc "dropCallBack"; setParent ..; window -e -wh 200 300 $win; showWindow $win; }
Now if you run that MEL script it should display a test window with four large buttons. Of course they won’t work yet as we’ve not defined our callback functions, but lets do that now…
global proc string[] dragCallBack(string $dragCtrl,int $x, int $y, int $mods) { print ("dragCallBack - Drag control: "+$dragCtrl+"\n"); return {"A","B","C"}; }
This is the DRAG callback. You can call it anything you like but it must have four input parameters (but again, as long as they are a string and three ints, you can call them whatever you like). This callback function is fired as soon as you MMB-drag on any UI control with it specified in its -dgc flag. The drag callback function MUST return a string array otherwise it will NOT register the drag/drop functionality at all!. If you’ve ever tried to get drag/drop working and been unsuccessful, chances are this was the reason!
Even if you don’t want your drag callback to return any data or messages it still has to return a string array, although returning an empty array is fine if you really don’t want any actual data.
The primary input parameter is: $dragCtrl. When the callback is executed this parameter contains the (full) name of the UI control you dragged on. To be honest I’ve never really needed this data as you also get it inside the drop callback, but it’s here anyway if you do need it for something. The $x and $y params pass in the screen x and y positions you clicked on and $mods holds any keyModifier values, so if you want to check the user is holding down SHIFT or CTRL or whatever you can. (see Maya’s getModifiers command for more details on modifiers).
You may find that in your production UI you have several different drop callbacks, but you can often recycle the same drag callback for all the UI controls. In all my UIs so far the drag callback does practically nothing, it simply exists to ensure that the dropping works, as you cannot have one without the other!
global proc dropCallBack(string $dragCtrl, string $dropCtrl, string $msgs[], int $x, int $y, int $type) { string $text = "dropCallBack:\n"; $text += (" Drag control: "+$dragCtrl+"\n"); $text += (" Drop control: "+$dropCtrl+"\n"); $text += (" messages: "); $text += `stringArrayToString $msgs ", "`; confirmDialog -m $text; }
This is the DROP callback and it’s the important one that does all the work. Basically, dragging something usually has no effect in itself right? it’s only once you let go and drop it that something happens! As with the drag function, you may call this anything you like, but make sure your function always has these same six parameters.
The first two are strings which contain the (full) names of the drag control and the drop control. These are important because you will always want to know what you dragged and what you dropped it on to determine what action to take. The third parameter is the message array. This simply contains whatever the drag callback returned. While this is often unused it is a way to pass your own data from the drag to the drop zone if you need to. The $x and $y parameters are the screen position of the drop and $type is supposed to somehow indicate move, copy and link, but I’ve never found any details of how to utilise this parameter. I’ve never needed to use any of these three last parameters and chances are you won’t either, but don’t forget they still have to be declared as function parameters.
Once you’ve input the UI code above and defined both drag and drop callbacks you should have a fully working Drag/Drop UI to play with.
You can legally drag/drop controls from any drag/drop UI to any other drag/drop UI, even between different windows and even when they have different callback functions. The only way to restrict what is permitted to be dropped on what is by making your drop callbacks check some attribute or property of the drag control and return if not “compatible”.
Unfortunately you cannot interact with the existing drag/drop controls inside Maya. For example you cannot drop a shelf button or a hypershade swatch onto a custom UI drop zone or drop your custom drag objects onto a maya drop zone (i.e. viewport, outliner, hypershade, etc..)
June 5, 2010
June 4, 2010
Labyrinth! Hanjie/Picross based puzzles
Labyrinth is a new puzzle I recently discovered that takes the logic of Picross/Hanjie and adds a new dimension. Here’s a very simple 6×6 sample:
Each labyrinth puzzle consists of an empty board with a series of numbers along the left and top edges and a Start(S) and End(E) point marked (as seen above). The goal is to fill in a complete labyrinth of walls, tracing a continuous path from Start to End, visiting every square on the board grid once and once only.
As mentioned before, solutions are derived using the same logic rules as Picross (trad. known as Hanjie) so if you’ve played either of those games before, labyrinth should be very easy to pick up.
Walls and path may only be drawn horizontally and vertically, the walls are drawn along the grid lines and the path is drawn from square to square (center to center).
The numbers given along each edge denote how many wall sections there are on that grid line, and how long each section is. For example:
means that along this particular horizontal grid line there are four separate sections of wall. The first wall is 2 grid units long, followed by a wall a single unit long, followed by a wall 3 grid units long and finally another wall 1 unit long. There must be at least one segment gap between each section of wall and the sections are always drawn in the order shown (i.e. 2 first (leftmost), then 1, followed by the 3, and finally (rightmost) a 1). Because the grid in this example line is only 10 units wide, there is only one possible solution:
and once we know where there is no wall we can mark the path in:
However, you will not always immediately know where all the wall segments go, for example the following line is not so conclusive:
Still, no matter where the 6-length wall actually is on this line we can be sure that the two central spaces must be occupied by it, so we can mark those in right away:
the remaining four sections of this wall will have to be deduced later by some other method but, for arguments sake, lets assume that sometime later on we are able to add a path line here:
we can now safely deduce that the first two squares must also be paths (otherwise we’d have TWO wall sections, one of 2 and one of unknown length, and our edge numbers for this line clearly show there is only a single wall of length 6). It is obvious now that the wall has to start from at least the fourth grid section, so we know it must run up to at least the ninth, as it’s 6 long. We’re only 1 square off completing this line now:
As you can see this is typical Picross/Hanjie logic.
Once we have one full section of wall in we can draw some of the path in. For example if we somehow end up with this:
It will immediately give us:
and then, because we can see that the only other wall on this row is on the right of the 4 wall we know that there can’t be another wall in the first square, so it must also contain a path:
the 1-long wall must be in one of the remaining 3 grid spaces.
We can see that drawing the walls helps us to determine the path, and drawing the path helps us to determine where the walls are.
Common Tips:
The path always starts from S and terminates at E and must visit every square on the grid once and only once. This is a key factor in solving every labyrinth puzzle as in certain common situations it helps dictate where a wall or path can or cannot be. For example, in any labyrinth there cannot be any T-junctions, crossroads or dead-ends (except S and E which have to be dead-ends!), therefore, in any situation where adding a wall would cause a dead-end you know it must be a pathway.
Corners – One situation where this crops up the most is in corners. Wherever and whenever you find an inside corner you can always draw the path straight away (unless it has a S or E in it!). Take the following scenario for example:
Without even bothering to check or consider where any other walls or path may be we can immediately fill in the paths inside each and every corner as so:
This then immediately provides us with another wall clue:
which in turn gives us another corner to fill the path in at…
Finally, if you consider the right-hand portion of the labyrinth in the above image, we can deduce that the path CANNOT run horizontally (highlighted in blue) as this would produce an invalid path loop. Therefore there must be a wall there, which in turn means the path must run downwards (highlighted in faint pink)
0 (zero) walls – If the wall sections number is 0, meaning there are no wall segments along this line you can immediately draw paths between every set of squares along this grid line:
This is also true whenever you have filled in all the given wall segments on a line:
means you can fill in the rest like so:
S and E side by side – If the S and E are ever alongside one another you can immediately draw a wall between the two as so:
Vertical grid lines follow exactly the same principles as all the examples above, except vertically! 😉
There should only ever be one possible unique solution per puzzle and no guessing is required.
Here’s a sample solution (to the example 6×6 labyrinth shown at the start). Notice how the path is a single continuous line from S to E and it visits every square on the board without splitting or crossing itself.
Finally, here’s a couple of easy 8×8 puzzle to try (dump them into photoshop or print them out)
I’ll be adding more puzzles frequently, and shortly a Maya python script to play them dynamically in 3D!!
Have Fun!