User Values

From The Foundry MODO SDK wiki
Jump to: navigation, search

Sometimes scripts need to present an interface or store persistent state in the config. In modo, this is accomplished with user.value and its associated commands. Plug-ins can directly access the config system or create custom commands, but may also wish to user user values.

User values are implemented through commands. User value definitions can be stored in the config file (usually one associated with the script), thus avoiding the need to redefine them on every launch. The current value of a user value can also be stored in the config file, thus allowing for persistence between sessions.

user.value

The user.value command allows existing user values to be queried and modified. These values can be stored in the config file, so they will persist between sessions, or they can exist only temporarily until modo exits, or momentarily, meaning until the script exits.

user.value name:string ?value:*

The name argument is the name of the previously defined user value, where value is the value of its queriable, dynamic datatype. Since the value argument is required, a dialog will open if it isn't set. This allows for simple dialog-based user interfaces that ask for single values. Since the argument is queriable, it can also be inserted into forms in the main UI.

Defining

The user.defNew command is used to define new user values. The new value's name cannot already be defined, and is case sensitive. The type is optional, and will default to the string datatype if none is provided. The datatype is provided as a string, such as float, distance, integer or string. The life determines how long the user value persists for, and is described below.

user.defNew name:string <type:string>
            <life:{config|temporary|momentary}

This example defines a new user value named RandomSel.coverage using the percent datatype for a script that randomly selects a certain percentage of polygons in the current layer:

user.defNew RandomSel.coverage percent

User Value Lifespan

The life argument of user.defNew determines how the user value is stored. There are three life spans available, config, temporary and momentary.

Config

The default life span is config. This means that the user value will be saved in the config file, and will persist between sessions. If you were to quit and restart modo, this user value would still exist. This is good for user values that you want to use in forms. Making changes to one with user.defNew or user.def or user.value update the state in the user config.

Transient

Config user values can be defined so that the default value is loaded from the config, but is not written to it. These are known as transient, and are described in more detail below.

Temporary

The temporary life span creates a user value that will exist only until you quit modo. No state is saved in the config, so the next time you run modo the user value will not exist. This is good for user values that only need to exist between runs of a script, but not across work sessions.

Momentary

The final class is momentary. These user values will exist only until the command that spawned them returns. This is useful in scripts when you want to present the user with a dialog for choosing a value, but do not want them to persist once the script has finished executing. Due to their volatility, these must be created from within scripts to be of any use.

Changing and Querying Values

Once the user value above has been created, it can be set to 50% coverage through user.value. As usual, this command accepts both raw values and can be used with square braces for values with units.

user.value RandomSel.coverage 0.5
user.value RandomSel.coverage [50 %]

Inside the selection script you can read that value back out using the question mark syntax, which in this case will return 0.5:

user.value RandomSel.coverage ?

Editing Definition Properties

This isn't quite optimal. If you were to put this value into a forms view, you would see that the name is displayed as the rather unfriendly "RandomSel.coverage". It would also let you enter negtive numbers, which doesn't make sense for a selection. Similarly, there's no point in setting the value above 100%. This is where user.def comes in.

user.def name:string
         attr:{username|type|list|argtype|min|max|action}
         ?value:string

User definitions can be changed at any time. For example, you may wish to temporarily clear the transient property so that you can change the default value before setting it back to transient, or you may want to temporarily remove the action so that changing a user value's value won't execute the command or script associated with it.

Human-Readable Labels

The user.def command allows extra properties of a user value to be read and set. First we'll change the username:

user.def RandomSel.coverage username "Coverage"

The username property also supports message table entries. Message table lookups use the format @table@msg@ for a dictionary lookup, or @table@@id@ for an integer ID based lookup. Using message tables allows the config files to be translated for different languages without editing the script itself.

Command Dialog Name

If you execute user.value without specifying a new value, it will open a standard command dialog. The title bar will simply read User Value in older versions of modo, but as of 401 it will display the username of the value, falling back to the internal name.

You can use user.def's dialogname option to change string displayed in the the dialog's title bar. As with the username option, you can use either message tables or a hard-coded string.

user.def RandomSel.coverage dialogname "Random Selection: Coverage"

Fixed Ranges

user.def can also be used to set limit the range of a numeric value. For example, to set the minimum and maximum on our user value:

user.def RandomSel.coverage min 0.0
user.def RandomSel.coverage max 1.0

Or if you prefer, as percentages using the square brace syntax:

user.def RandomSel.coverage min [0.0 %]
user.def RandomSel.coverage max [100 %]

Now the nice label Coverage will appear when the command is queried in a form, and it cannot be given a value below 0% or above 100%.

Datatype

The type property can be used to change the datatype of the value, or to simply read the current datatype back out. Changing the datatype clears out any existing value.

user.def myUserValue type ?
user.def myUserValue type integer
user.def myUserValue type percent
user.def myUserValue type string

Transient

Transient is a modifier on config lifespan user values. Like config user values, the definition is stored in the config file, and the default value is read from the file if found. However, transient user values are not saved to the config file itself. This means that any change the user makes is temporary, or transient, and upon restarting the application the user value will have its default value.

user.def myConfigUserValue transient true

Choices

Lits of choices are supported for user values. They are often static, but can be dynamic if necessary. Internally, list options are treated as TextValueHints.

Static Choices

The list property is useful for creating a static list of choices for an integer-type value. Each option in the list is separated by a semicolon. This example lets you pick from top, bot, left and right. Querying the value will return one of those strings in 401 and later (prior to 401, this would return the string's index: 0, 1, 2 and 3 respectively). Note that it is still possible to set out-of-range values by specifying an integer directly instead of using one of these keywords, so the script must be able to handle those cases. This means that even in 401, you may get back an integer when there is no string at that index.

user.defNew AlignWithEdge integer
user.def AlignWithEdge list top;bot;left;right

If this value was opened in a dialog by omitting the value argument:

user.value AlignWithEdge

Then a popup is created. However, the popup shows these lowercase, internal names; its not really what you want to have to present to the user. The argtype property allows an ArgumentType entry from the config to be used to provide human-readable names for the options:

user.def AlignWithEdge argtype "AlignWithEdge-mode"

Now when the command dialog is opened, nice human-readable names like "Bottom" and "Left" are pulled form the AlignWithEdge-mode ArgumentType entry in the config, instead of the more imposing "bot" and incorrectly capitalized "left".

Procedural Usernames

It is generally preferable to use the argtype property to set the usernames for the list property, but this is only viable for static lists. Dynamically-generated lists can set usernames via the listnames property. Like lists, this takes a semicolon-delimited list of strings. Two semicolons can be used to insert a single semicolon into the name. Any characters are allowed in the name string.

user.def AlignWithEdge listnames "Top;Bottom;Left;Right"

list Considerations

Lists are meant to for internal strings that are not displayed to the user directly, but are instead used by your script to identify a particular choice. The human-readable strings presented in the interface are provided through the argtype property and ArgumentType config entries as described above or through the listname property, and can contain any characters. The argtype property has the added advantage of supporting translation into other languages without modifying the script itself.

The internal strings, however, have specific limits on the kinds of characters that are allowed. Some characters are used for special behaviors, while others are removed for simplified parsing and management.

The following characters are reserved and cannot be used anywhere in the string:

  • +
  • &
  •  %
  • @
  • =
  • [space]
  • underscore

Furthermore, the internal name cannot start with a number, as it is possible to select entries in the list by value (index) instead of by name, and a leading number would confuse the parser.

Attempting to use these in a list will fail or result in unpredictable behavior.

Value Presets

Value Presets were introduced in modo 401. This creates a popup to the right of certain control types (notably edit fields) that contain a user-extensible list of named presets. Controls created by scripts can also support this feature through the use of a valuepresetcookie.

user.def myValue valuepresetcookie "myCookie"

The cookie is just a string that is used to lookup and store value presets in the config. All of this work is handled for you simply by setting the cookie. The cookie need not be unique, and multiple controls can use the same cookie (and thus the same list of presets).

Responding to Value Changes

The most interesting of all the user.def options is action. Whenever the value is changed through the user.value command, the action associated with it will be triggered. This action can be any command or script. For example, we could have our random selection script run every time the minislider is tweaked in a form view:

user.def RandomSel.coverage action "@RandomSel"

This is a very powerful feature. One of the functions of the commands and forms systems is refiring. In the case of the RandomSel script, this means that you can drag the minislider and watch the selection interactively update. The script feels just like its part of the application.

It is important to take care to avoid recursion issues when using this capability. For example, say you have three buttons based on user values, each of which have an action associated with them. This action changes the user value associated with the other two buttons. Since they also have actions applied, they will each be triggered, changing the value of the other two buttons (one of which is the originally clicked button), and so on. To avoid these issues, be sure to temporarily clear the action option from the user value before you change its value.

Delaying the Action

Normally, any action associated with the user value occurs as soon as the value change. This provides real-time interactive feedback while the user drags a minislider, for example.

While this is a useful behavior, it is not uncommon for complex scripts to take a relatively long time to execute. In those cases it's more desirable to defer the action until the user has stopped interactively manipulating the control. By setting the user.def property deferaction to 1, a user value's action will not trigger until the mouse button is released from the minislider. This feature can significantly improve the apparent interactivity of your user interface, although it is at the expense of immediate feedback.

user.def  myValue defer action 1

Enabling or Disabling

User values can be enabled or disabled by using the enable state or value of another command. This is done with the user.def property enablecommand. The value is a command string to test, most commonly another call to user.value.

The rules for if a user values is enabled are:

  • The enable command must itself be enabled
  • If an argument is marked for query (i.e.: with a question mark in place of its value), then the argument value must be non-zero for a numeric control, true for a boolean, or a non-empty string for a string control.

If both of those conditions are true, the control is drawn as enabled.

For example, if you have a boolean user value with the name turnOnOption, and you want it to toggle your myOption user value, you might do this:

user.def myOption enablecommand "user.value turnOnOption ?"

Now myOption will be shown as enabled only when the user.value turnOnOption ? command string is both itself enabled and the turnOnOption user value is true. The notifiers on the enable command will be used to automatically update the user value's enable state as needed.

Notifiers

Notifiers are used by commands to let other clients (notably the Forms system) to update when something has changed. The most common case is updating the value or enabled state of a control. The user.value command already contains a notifier that updates the form when its value changes, but it can be useful to add extra notifiers from time to time.

This works much like enable commands, where the notifier property is used with user.def to set the command string. The notifiers from that command are then applied to the user.value instance in the form.

Default Values

The user.default command rests user values to their default value. This uses modo's Pattern matching, allowing a swath of command to be reset at once. Normally, the default value is 0 or an empty string. You can set an alternate default value with the user.def defaultvalue property.

UI vs. Undoable User Values

By default, user values are undoable. In some cases, such as preferences, you want the user value to not be undoable, and instead to be represented as a UI command. This can be done with the user.def asUI property. When set, the command wills how up in the Command History's undo list as a UI command, and hitting undo will not undo it.

Creating Mutually Exclusive or Toggle Buttons

Various controls in modo operate as mutually exclusive buttons (also known as "radio buttons"), where only one of a series of options can be active at any time. Others operate as toggle buttons, such as the many tool buttons in modo. These are similar to boolean checkmark buttons, but the label is on the face of the button. A tool-style button can be made from a boolean-type user value via the Form Editor, but to get mutually exclusive style behavior, you'd either need to have a custom action script handle changing the state of the other buttons. Or, you can use user.toggle.

user.toggle name:string valueOn:integer <valueOff:integer>
            toggle:boolean

user.toggle requires an integer user value that uses lists. Such values are defined in a manner similar to this:

user.defNew myValue integer
user.def myValue list "a;b;c;d"

Don't forget to specify usernames with the argtype or listnames properties.

Inserting this into a form as user.value myValue ? will create a popup. To create a toggle button for a single option, you can use user.toggle. A very simple on/off style button could be created like so, if you didn't want to use the boolean datatype.:

user.defNew onOff integer
user.def onOff list "on;off"

Here is an example of how user.toggle works. The toggle argument simply changes the current value to either valueOn or valueOff. This argument can also be queried. You can execute user.toggle like so to set the value to on:

user.toggle onOff on off 1

Meanwhile, this will change the value to off:

user.toggle onOff on off 0

When used in a form, the toggle argument is automatically queried for its value (since it is defined as a ToggleValue argument) and is used to decide if the button is on or off. Clicking the button will then execute user.toggle with the toggle argument set to valueOn or valueOff as appropriate.

Note that since toggle is a ToggleValue argument, when you insert it into a form you need to set it to 1. Do not use a question mark, as this will not work.

user.toggle onOff on off 1

The above creates a single button. If you want a series of mutually exclusive buttons, just create more buttons in the form. If you had a user value defined like so:

user.defNew threeChoices integer
user.def threeChoices list "yes;maybe;no"

You can insert these three commands into a form. To create a traditional radio button style interface, you may want to use a horizontal toolbar.

user.toggle threeChoices yes toggle:1
user.toggle threeChoices maybe toggle:1
user.toggle threeChoices no toggle:1

Notice that we skipped the valueOff argument. This means that clicking on a currently "on" button will not turn it off again. Since clicking on any of the currently off buttons changes the user value used by all three of the buttons, that one button will go on and the other two will go off. Note that any associated action will only be triggered for the user value of the one button that just changed state, not for the other two buttons that weren't explicitly clicked on.

Creating User Values From Within Scripts

User values used in forms are defined in configs. You can have a script create these user value directly rather than shipping them in a config with your script. This is most often done for the short-lived temporary and momentary lifespan user values, as opposed to config lifespan user values. Creating config-level user values from a script is somewhat legacy, as you usually will ship a config with your script for its UI, [[Kits|kit], etc. anyway.

 #perl
 
 # See if the user value exists
 if( !lxq( "query scriptsysservice userValue.isDefined ? MyValue" ) ) {
     # Value doesn't exist; create it with calls to user.defNew
     #  and user.def, and give it an initial value with
     #  user.value
     lx( "user.defNew MyValue integer" );
     lx( "user.def MyValue min 0" );
     lx( "user.def MyValue max 10" );
 }
 
 # See if a value has been set yet
 if( !lxq( "query scriptsysservice userValue.isSet ? MyValue" ) ) {
    lx( "user.value MyValue 5" );
 }

An important detail of this example is that it checks to see if the value is set before calling user.value. This is because user.defNew and user.def are side effect commands -- they are not undoable, but user.value is so that it can support refiring. This means that if the user undoes the script, the value will be restored to its default, while the definition would remain, and future executions of the script would have the uninitialized value instead of the default you intended.

If you simply need the user value to exist, you can just create it directly with user.defNew. To avoid the error dialog that would normally appear, you can use the ! or |!! syntax to suppress it. This has limitations, however: the datatype of the existing value might not match the value you want; you won't want to just replace the existing user value; and changing the properties with user.def may cause any existing value to be reset.

 #perl
 
 # Just create the user value, ignoring any errors (like if it
 # already exists). This might cause unexpected behavior if the
 # value exists, but is of a different datatype than what you
 # expect.
 lx( "!user.defNew MyValue integer" );

You can also use scriptsysservice to tell if the user value has been set yet and what its lifespan is.

More Information