Advanced Techniques

Keeping Things Moving

With a basic zscript ZBrush will carry out the commands in order, one after the other, but anybody who has worked with the program will have realised that some things are only possible after other things have been done first. When writing a more complex zscript it is important to anticipate the state that ZBrush could be in, so that an attempt to carry out the script actions does not fail. Checking the state of the ZBrush interface is one way that we can keep a zscript moving.

There is another reason why we might want to organize our code so that it can cope with a variety of situations: giving the user a choice. It might be that we want the user to be able to select a file. We will then need to write code to handle two situations – one where we deal with the selected file, and a second where we cope with the user changing their mind and not selecting a file at all!

If… this Happens then Do That

What this boils down to is a general expression ‘if this happens, then the code must do this… otherwise it must do that’. The If command is perfect for these circumstances. It is quite straightforward:

[If,/*condition to check goes here*/,
/*commands if condition true go here*/
,//else
/*commands if condition false go here*/
]//end of if command

Supposing we wanted to check whether there was a model in Edit mode before carrying out a series of commands? The Edit button in the Transform palette is only on when there’s a model in Edit mode, so we can use that as the condition we check for. But how do we test that in our code? Well, first we need to find out about the Edit button’s states. To do that we can use a Note.

Spreading the Word: Notes and Messages

There are various ways that a zscript can display a message to the user; one of the most useful is the Note command. (You may have already come across this in a zscript tutorial.) The basic form is this:

[Note,"message to user" ,, display duration ]

(Note that there are two commas between the message and the duration.)

You can use a Note anytime you want to tell the user something. The only requirement is that it must be wrapped up inside a button or some other ‘container’ type command.

Because a Note displays a message we can use it to display information about the ZBrush interface. That means we can write a test zscript to find out the state of the Edit button. This is how we do it:

[VarDef,editMode,0] //defines a variable to store Edit mode
[IButton,"Test Edit","Test Edit button for its State", //beginning of button code

[VarSet,editMode,[IGet,Transform:Edit]] //gets value of Edit button
[Note,editMode,,3] //displays the value of Edit button for 3 seconds
] //end of button

If you try out this zscript you’ll find that when the Edit button is switched on the value is 1 and when it’s off the value is 0. Having found out this we can use those values in our code. So, for the If command we looked at earlier we could write:

[VarDef,editMode,0] //defines a variable to store Edit mode
[IButton,"EditMode","Display Edit Mode to User", //button start

[VarSet,editMode,[IGet,Transform:Edit]] //gets value of Edit button
[If,editMode = 1, // test value

[Note,"Edit is ON",,3] //commands for when Edit is on
, //else (when editMode is 0 and Edit button is off)

[Note,"Edit is OFF",,3] //commands for when Edit is on
]//end of If command
]//end of button

Loops – Just Keep Doing It!

If you spend much time writing zscripts, sooner or later you will have a bit of code that you want to repeat a number of times. The way to do this is by using the Loop command. Here it is:

[Loop,/*number of times*/,
/*commands go here*/
]//end of Loop

All we need to do is wrap the Loop around the commands we want repeated, specifying the number of times to repeat the code. Here’s an example:

[VarDef,counter,0] //defines a number variable called counter
[Loop,10, //loop set to 10 times

[VarSet,counter,(counter+1)] //adds 1 to the value of counter variable
[Note, counter,,1] //displays value of counter for 1 second
] //end of Loop

Spreading the Word: Strings

As you may remember from when we discussed them earlier, variables can be of two types – those that store numbers and those that store strings.

Strings are simply groups of characters such as words or phrases. A variable can store any number of characters from none at all – what’s called an empty string – to 255. It’s important to remember that when you count the characters in a string that you also count the spaces as well; so the phrase “Hello World” has eleven characters, not ten. If you exceed the 255 limit in a single string ZBrush will show an error message and your zscript will fail.

Whenever you use a string in a zscript it is good practice to enclose it in double quotes "Like this". This not only identifies it as a string but ensures that ZBrush doesn’t ignore the spaces when displaying it.

To define a string variable, simply put an empty string (“”) as the initial value:

[VarDef,myString,""]

At some point you will probably want to combine two or more strings together. This can be done using the StrMerge command, which, as its name suggests, merges strings together:

[StrMerge, //first string, //second string]

Up to twelve strings can be merged at one time, each being separated by a comma, but remember that the merged string must still be no more than 255 characters in length.

We could now write a new version of our Loop example:

[VarDef,counter,0] //defines a number variable called counter
[VarDef,message,""] //defines a string variable called message
[Loop,10, //loop set to 10 times

[VarSet,counter,(counter+1)] //adds 1 to the value of counter variable
[VarSet,message,[StrMerge,"The counter is now at : ",counter]] //merges string with value of counter and stores it in message variable
[Note, message,,1] //displays message for 1 second
] //end of Loop

There may be occasions when you want to display a Note but your message is over 255 characters in length. There is a simple way of overcoming this problem. Any Note with a display duration of -1 will be combined with the next Note, so you simply divide up your message into chucks of 255 characters or less and spread the chunks over a number of Notes:

[Note,"This message will be displayed at the top of the next Note.",,-1]
[Note,"\nThis is the end of the message.\nThe combined Note displays for 5 seconds.",,5]

(Note: The special character combination \n used above creates a new line.)

Apples, Pears, Oranges – the Joy of Lists

If you have a series of values that you want to store then the ZScript language provides a list variable so that you can do it. This can be very handy for storing such things as different texture widths or lists of file names. The variable is defined like this:

[VarDef,textureWidth(10),0]//defines a list variable of 10 numbers
[VarDef,fileName(10),""]//defines a list variable of 10 strings

You would then set the value of one of the list like this:

[VarSet,textureWidth(9),1024]//sets value of list variable at index 9
[VarSet,fileName(0),"Franks_Head.ztl"]//sets value of list variable at index 0

You’ll have noticed that in the example above there is a variable with index 0. That is quite correct. In common with many programming languages, ZScript starts an index at 0, so the tenth item is actually the variable with index 9. There is therefore no index 10 (for a list variable of 10 items) and if you try to use it ZBrush will return an error.

Economy Time – Routines

By now you will have grasped the essentials of zscripting and be able to write fairly complex zscripts. If you have experimented at all, you will probably have found that there are times when you have to duplicate bits of code. For example, it may be that you have several buttons that all check to see if there’s a model in Edit mode before running through their different commands. Routines provide a way of reducing code duplication of this sort. In the process they make the zscript easier to understand and therefore easier to correct when things go wrong. Also, if you do have to change anything, it only has to be done once, in the routine, saving a good deal of time and effort.

Routines are very simple to use. They act like ‘boxes’ for the bits of code that you want to reuse. Whenever you want to use the code in that particular box, you simply call the routine. ZBrush runs through all the code in the box before returning to the button or wherever it was that you called the routine from. It then continues with the commands that follow after the call.

You create the box by defining the Routine like this:

[RoutineDef,routine name,//defines a routine
/*commands go here*/
]//end of Routine

For a routine that checked the Edit button and then stopped the zscript if it was ‘on’ we might write:

[RoutineDef,CheckEditButton,//defines a routine called CheckEditButton
//start of command block (a series of commands run one after another)

[VarSet,editMode,[IGet,Transform:Edit]] //gets value of Edit button
[If,editMode = 1, // test value

[Note,"Tool is in Edit mode. ZScript aborted",,3] //commands for when Edit is on
[Exit]//exits the zscript
]//end of If command
//end of command block
]//end of Routine

We could then use the RoutineCall command to call the routine anytime we needed like this:

[RoutineCall,CheckEditButton]//calls the routine named CheckEditButton

 

Save/Load stuff – Using Files

Being able to work with files adds greatly to the power of zscripts. There are many different commands for file operations but we will only examine a few of them here.

FileExists is very useful for checking that a named file is actually present on the user’s computer. By using FileExists along with an If command it is possible to avoid the zscript stalling should the named file be absent. Here’s how it might be used:

[If,[FileExists,"MyModel.ztl"], / / test to see if file "MyModel.ztl" exists

[Note,"File is present",,3] //commands for when the file is present
, //else file isn't present

[Note,"File is missing!",,3] //commands for when the file is absent
] //end of If command

Checking if a file is present is not much help unless the file can be used in some way. FileNameSetNext is the command we need: it sets the file name for the next save or load action. To load a particular image file as a texture we could write:

[FileNameSetNext,"MyTexture.psd"] //sets "MyTexture.psd" as the next file to be saved/loaded
[IPress,Texture:Load] //loads "MyTexture.psd" into the Texture palette

File paths

When specifying a file it can be important to include the path. If there is simply a file name (with no path) then ZBrush assumes that the file resides in the same folder as the executing zscript.

The path can be relative or absolute. An example of a relative path is:

"MyZScriptData\Settings.zvr"

which specifies a file “Settings.zvr” in a folder “MyZScriptData” which is a subfolder of the same folder where the executing zscript resides.

An example of an absolute path is:

"C:\Program Files\Pixologic\ZBrush3\ZScripts\MyZScript.txt"

The problem with absolute paths is that you can’t be sure that a user’s files are in the default locations. Fortunately ZBrush provides a way to overcome this, by using the special form:

"ZBRUSH_ZScripts\MyZScript.txt"

which can be used for any of the default ZBrush folders.

Files and Variables

You can store file paths in variables; simply define them as of string type:

[VarDef,filePath,""]

Variables can also be saved out as files. This is particularly useful for saving settings that the zscript can then load when it is run. The pair of commands is VarSave and VarLoad. To save a variable named “myVariable” to a file named “mySettings.zvr” the code would be:

[VarSave,myVariable,"mySetting.zvr"]

And to load the value back in:

[VarLoad,myVariable,"mySetting.zvr"]

(*.zvr is the default file extension for ZBrush data files.)

List variables can be saved/loaded in the same way. You simply put the name of the list variable without its element indicator, so for a list variable defined as myListVariable(10) the code to save would be:

[VarSave,myListVariable,"mySetting.zvr"]

Total Recall – Memory Blocks

Memory blocks are simply chunks of computer memory. They can be very useful. For example, if you understand file formats it is possible to read and write files using zscript-created memory blocks, and so manipulate the file data directly. But that is beyond the scope of this brief introduction; here we shall explore only one use for memory blocks: storing numbers. You might be wondering how that is different from using variables. Well, the big advantage of memory blocks is that they are what’s known as persistent, meaning that they remain available throughout a ZBrush session (unless they are deleted). Variables are not persistent and their values are reset each time the zscript exits. This can happen frequently as only one zscript or plugin can be active at a time. So, if you want your zscript to store values for recall later, you need to use a memory block.

Store Now, Use Later

The example we’ll explore here is for storing a 3D model’s position on the canvas.

First we need to create the memory block to store the values. In zscripting, a memory block is created by naming it and specifying its size. There are a couple of ways of doing this but here we shall use the simplest, the MVarDef command. This command is really just a way of creating a ‘memory block variable’. It is important to remember that it will only store numbers.

When naming memory blocks it is a good idea to use a name which is as specific as possible. This is because other zscripts or plugins could access your memory block by mistake if the names happened to be the same. Good practice is to use your initials and a name specific to the zscript. For example, if your name is James Bond and the zscript is called ZCasino the memory block could be called JB_ZCasinoData.

Note: memory block names are an exception to the general rule in zscripting: they are case-sensitive.

When using MVarDef to create a memory block we simply need to say how many values we want it to store. This can be as little as one but as there are nine transform values (of position, scale and rotation) we’ll use nine for this example:

[MVarDef,JB_ZCasinoData,9]

As with list variables, the ‘box’ for each value is specified using an index number, starting with 0 for the first value. To store ‘775’ at the first index we would write:

[MVarSet,JB_ZCasinoData,0,775]

And to get the value stored at the ninth index:

[MVarGet,JB_ZCasinoData,8]

You may remember that to get the transform values of a 3D model we can use the TransformGet command. We could use that now to get the values for our memory block but there is an easier way – the MTransformGet command. This gets the values, in just the same order, and puts them straight into the memory block:

[MTransformGet,JB_ZCasinoData]

To set the model with the stored values we use the MTransformSet command:

[MTransformSet,JB_ZCasinoData]

Garbage Collection

A memory block that’s finished with is garbage. If it is not got rid of, then it’s using valuable resources that might be needed elsewhere; it’s good policy to delete memory blocks as soon as they’re done with. The command for this is MemDelete.

[MemDelete,JB_ZCasinoData]

However, attempting to delete a memory block that doesn’t exist will cause an error, so it’s a good idea to check first that the block exists. The MemGetSize command (which gives the size of the memory block) will return 0 if a memory block doesn’t exist and so can be used in this way:

[If,[MemGetSize,JB_ZCasinoData], // test for memory block, returns 1 or more if exists

[MemDelete,JB_ZCasinoData]//deletes memory block
, //else (when memory size is 0 and so doesn't exist)

[Note,"No memory block found",,3]
]//end of If command

It is good practice to use this method for all memory block actions, including when creating a memory block:

[If,[MemGetSize,JB_ZCasinoData],

//does nothing - memory block already exists
, //else (when memory size is 0 and so doesn't exist)

[MVarDef,JB_ZCasinoData,9]//creates memory block
]//end of If command

 

Building Interfaces

If you are going to spend some of your valuable time writing a zscript or plugin then you are going to want it to be usable. There is not much point in writing the thing at all if the users can’t get it to do what they want. Just providing the right buttons or sliders might not be enough. For a complex zscript you will want to arrange them in some way which makes the whole thing easy to understand and to use.

Interface design is a complex subject which we won’t discuss here. We will simply outline the zscripting possibilities.

 

The ZScript Tutorial Window

The Tutorial Window is at the bottom of the ZBrush interface. You open it by clicking or dragging the handle, or pressing the hotkey ‘H’. The ‘Play’ button for zscript recordings displays here and it is the default location for any zscript buttons and sliders.

Attractive and functional interfaces can be designed for the Tutorial Window. To learn more about Tutorial Window interfaces, see ZScript Tutorial Window Interfaces.

ZPlugin Interfaces

By making your zscript into a ZPlugin you can add buttons to nearly all of the default palettes of the ZBrush Interface. This has many advantages; for example, it is possible to use zplugin buttons in any custom interface arrangement. However, zplugin interfaces are somewhat limited from a design point of view.

To learn more about ZPlugin interfaces, see ZPlugin Interfaces.

Note Interfaces

An example of a Note Interface is the Projection Master dialog. Note interfaces are an excellent way of displaying images and getting user input. As images can be used for the background and buttons, they can be made to look any way you want. Note interfaces don’t exist on their own and must be called from a zscript or plugin button.

To learn more about Note interfaces, see ZScript Note Interfaces.

Where to Next?

If you’ve got this far, then you are well on the way to being an accomplished zscripter. This section briefly explores the options for extending your knowledge and developing your talent.

Special Text Editors

If you’ve got the zscripting bug then you may want to consider getting a text editor that is designed for programmers. Among their many useful functions perhaps the most useful is Syntax Highlighting whereby the different commands are displayed in different colors, making reading and editing the code very much easier.

Here is an example from UltraEdit:

UltraEdit text editor

And a selection of text editors:

JEdit
TextPad
UltraEdit

For a discussion of text editors and some useful information, see here and here.

ZScript Command Reference

There are nearly two hundred ZScript commands. For a description of each command with an example of how to use it, see the ZScript Command Reference.