Scripted Custom Attributes

Topic: version 4 MAXScript New Features/Custom Attributes

Custom Attributes let you create and assign additional parameters to any object, modifier, or material in your scene. This is useful for game and other project-specific data.

MAXScript provides a way to define and add these custom attributes to objects in the scene and also a way to define UI rollups for accessing these attributes.

Attributes can be added to an object via an "attribute definition". This is a new syntactic construct in MAXScript that is similar to a scripted plug-in definition, but rather than adding a new plug-in class, a definition is built that can be used to add custom attributes and rollup UI to any number of objects at any time, through a special new function.

Note:

You cannot use Custom Attributes for per-face-data. The face-data channels store objects of a user defined type and the scripter cannot create objects of types other than those known by MAXScript.

The new syntax has the following form:

attributes <varname> [version:n] [silentErrors:t/f] [initialRollupState:0xnnnnn]

(

<locals>

<globals>

<parameters>

<rollouts>

<handlers>

)

Remarks

The name is now simply a descriptive name for the definition and can be either a <name> or a <string>.

Example:

attributes "Game Data"

(

...

)

or

attributes gameData

(

...

)

The attribute header keyword parameters and main clauses are basically identical to the same keyword parameters and clauses in Scripted Plug-ins. The 'custom attributes' are effectively the parameters defined in any parameter clauses in the body of the attributes definition. The UI for them is defined by the rollout clauses, which will be automatically displayed in the Command Panel and Material Editor when an object containing custom attributes is selected.

Example:

attributes weaponData

(

parameters main rollout:params

(

hitPoints type:#float ui:hits default:10

cost type:#float ui:cost default:100

sound type:#string

)

rollout params "Weapon Parameters"

(

spinner hits "Hit Points" type:#float

spinner cost "Cost" type:#float

dropdownlist sound_dd "Sound" items:#("boom", "sparkle", "zap", "fizzle")

on sound_dd selected i do sound = sound_dd.items[i]

)

)

Remarks:

This defines three new attributes, say for weapon objects in a game level editor set up. The attributes, hitPoints, cost and sound, are defined as parameters and the UI for them in a rollout.

Note: The auto-UI connection between the parameters and rollout items via the ui and keyword is the same as the support in Scripted Plug-ins. You can treat attribute scripting as basically identical to scripting plug-ins, in terms of parameter, rollout setup and handler programming.

Adding Attributes to an Object

custAttributes.add <object_or_collection> <attributes_definition>[#unique]

Adds a set of attributes to an object or a collection. You can only add one custom attribute set of a particular attributes definition to an object, but you can have as many different attribute sets from different definitions as needed.

#unique

When adding custom attributes to an object using the custAttributes.add() function, you can now supply an optional #unique keyword as the third argument. If you do this, each object being added to will have custom attributes added that have their own private copy of the definition and do *not* share the original definition. Any later redefinitions of the original attributes definition will not update these custom attributes. To update the individual object's attributes, you would get the unique definition from the object using custAttributes.getDef, and redefine it in one of the two ways described.

Note: If you extract the uniquely-added definition, and use it directly in another non-unique custAttributes.add() on some other object, a definition sharing will be set up. Using #unique when adding a global definition will make the added custom attributes non-global, they will have their own private definition which starts out being identical to the original global definition.

Example:

custAttributes.add $weapon01 weaponData

Opening $weapon01 in the Modify panel will now display the "Weapon Parameters" rollout and allow them to be edited as can normal object parameters. You can use attribute definitions to add their defined sets of attributes to any object at any time. Any appropriate type of parameter can be animated, also, exactly as with scripted plug-ins.

The custAttributes.add() function can be applied to an object collection to add sets of attributes to a group of objects in one go.

Example:

custAttributes.add $weapon* weaponData

Adds a separate set of these custom attributes to all objects whose names begin "weapon" in the scene.

Note: Further, an object can have any number of separate sets of custom attributes added from different attribute definitions.

As with Scripted plug-ins, an attributes definition can have any number of sets of parameter and rollout clauses, to allow you to organize added attributes into multiple rollouts as needed.

Scripted custom attributes added to an object or modifier can be accessed in MAXScript. They turn up directly as properties on the objects they are added to. If the names of any of the custom attributes are the same as existing properties on the host object, the custom attributes are effectively hidden when accessed directly. As an alternative, you can access each added block of custom attributes by their attributes definition name and then access individual attributes as properties within that block.

So, for example, if the following attributes were added to $box01

the_weaponData = attributes weaponData

(

parameters main rollout:params

(

hitPoints type:#float ui:hits default:10

cost type:#float ui:cost default:100

sound type:#string

)

    

rollout params "Weapon Parameters"

( ... )

)

the custom attributes could be accessed as

$box01.hitPoints

$box01.cost

$box01.sound

or indirectly through the attributes block:

$box01.weaponData.hitPoints

$box01.weaponData.cost

$box01.weaponData.sound

This acts in a similar way to class IDs in scripted plug-ins. Such definitions are global across any number of scenes that custom attributes may be present in and all instances of custom attributes of a global definition will have the same shape. The ID is specified with a new header parameter, 'attribID:'

attributes <name>

attribID:#(<number>, <number>)

(

...

)

The ID number pair is chosen to be unique, perhaps generated with the genClassID() function in MAXScript. Attribute definitions without attribID's are private to the current MAX session.

genClassID()

This method generates a random class ID similar to #(0x9b7ea231, 0xb6db86ef), and prints it to Listener. You can just cut and paste this class ID into your script to use the generated ID.

Global and Private Definitions

Definitions are identified now depending on whether they are global or private definitions. Global definitions have explicit attribID: parameters, which define a globally unique ID for that definition and ensure that any object with custom attributes based on a definition with that ID will be follow the same definition. Whenever you evaluate an attributes definition with a particular attribID: you will update all objects that have custom attributes based on that definition, and loading objects with attributes of that attribID will update themselves to the current definition.

Private definitions have no explicit attribID: parameter (they effectively have an internally-generated random one). Each time you evaluate one, except as described in the redefinition section below, a completely distinct definition is created with its own internal ID.

In the case of both global and private definitions, the 'attributes' definition construct now returns the definition as its value, so you would typically assign that value to a local and then use it to add custom attributes to objects via the custAttributes.add() function.

Example:

local def

def = attributes "Game Data"

(

parameters ...

rollout ...

)

custAttributes.add $box01 def

custAttributes.add $box02 def

This example adds a separate set of the defined custom attributes to box01 and box02. The definition is shared between the two boxes. Now this is a private definition, it has no attribID:, so if you evaluated the 'def = attributes (...)' again, it would *not* automatically update box01's and box02's attributes, as used to happen in prior builds, but create a new definition. In order to redefine a specific attribute definition so that all objects will be updated that had attributes added using it, you use one of two schemes that operate on the actual definition value (as it sits in the 'def' variable in the above, for example). First, you can use the new 'redefine:' attributes definition parameter.

Example:

attributes "Game Data"

redefine:def

(

parameters ...

rollout ...

)

This expects an existing attributes definition as the value given in the redefine: parameter and will update it to the definition that follows, and update any custom attributes made from that particular definition value. In the example, this would update $box01 and $box02.

Alternatively, you can use the new custAttributes.redefine method:

    custAttributes.redefine <def> <definition_string>

Example:

local new_def = "attributes \"Game Data\" ( ... )"

custAttributes.redefine def new_def

This is particularly useful in situations where you are generating the attributes definition automatically as a string. You can use this function to compile and apply the redefinition to a particular definition object in one go, without having to construct a name for the object, as you would if you used the redefine: parameter scheme.

To redefine the attributes on some object, say based on the defData in that object's custom attributes, you might use this sequence:

old_def = custAttributes.getDef $box03 1 -- get existing def

old_def_data = custAttributes.getDefData old_def    -- get my defData from it

new_def_string = generate_new_def old_def_data ...  -- make new def string

custAttributes.redefine old_def new_def_string      -- redefine it

These redefinition techniques can be used with global definitions (with explicit attribID:), but are strictly unnecessary since the particular definition value is specified uniquely by the attribID: parameter. In any situation that you evaluate a global definition, all custom attributes in the current scene made from that definition will be updated.

Custom Attribute Management Functions

custAttributes.count <obj>

Returns a count of the number of separate scripted custom attribute sets have been added via the .add() function.

custAttributes.get <obj> (<index> | <attrib_def>)

Returns the custom attribute set, specified as an index or by its defining attribute definition.

Note: The custom attribute set values, returned by the custAttributes. are effectively holder values for the object's custom parameters in that attribute set. You can get at these parameter values as simple properties on the attribute set value.

Example:

gp = custAttributes.get $ gameParams

gp.hitPoints = 50

This is equivalent to accessing the custom attribute parameters directly on the object:

$.gameParams.hitPoints = 50

custAttributes.delete <obj_or_collection> (<index> | <attrib_def>)

Deletes the specified custom attribute set from the object or from all the objects in the given collection. The set to be deleted is defined by index number or by its defining attributes definition.

custAttributes.makeUnique <obj> (<index> | <attrib_def>)

If some objects with custom attribute sets share a specific attributes definition, you can make selected objects have unique copies of the definition.

Example:

custAttributes.add $box* def1

You can make box01 unique with:

custAttributes.makeUnique $box01 def1

or all of them unique with:

custAttributes.makeUnique $box* def1

custAttributes.getDef (<obj> <index>) | <custAttrib>

Returns the attribute definition for a given custom attribute set in an object or from a custom attribute set accessed with the .get() method.

custAttributes.getDefs <obj>

Returns an array of definitions in attribute set order.

Attribute definition values

Attribute definition values now have several properties directly accessible:

<def>.name    

Get and set descriptive name

<def>.source   

Read-only, the source code for the definition

<def>.defData  

Get and set the user-supplied def data value

<def>.attribID

Read-only, a two-element array containing the attribID for the def

custAttributes.getDefSource <attrib_def>

Returns the source code for the attributes definition as a string.

custAttributes.setDefData <def> <data>

Adds user-supplied data to an attributes definition.

The <data> value supplied here will be stored persistently with the definition and loaded on scene loads containing attribute sets of this definition.

custAttributes.getDefData <attrib_def>

Returns the saved user-supplied data.

custAttributes.getPBlockDefs <attrib_def>

These methods return an array containing a runtime readable encoding of all of the parameter block definitions in the custAttributes definition. The array is presented in the following form:

#(<paramblock>, <paramblock2>, ...)

<paramblock>

An array of details for one parameter block in this form:

#(<name>, <id>, <refno>, <keyword_params>, <parameter1>, <parameter2>, <parameter3>, ....)

<id>

The parameter block's internal ID number

<refno>

The reference number of the parameter block in the owning plug-in instance

<keyword_params>

An array of the keyword parameters given on the parameter block definition in the form:

#(<keyword>, <value>, <keyword2_name>, <value2>, ...)

<Parameter1 to ParameterN

Definition details for each parameter in the block in the following form:

#(<param_name> <keyword_params>)

<keyword_params>

An array containing all of the keyword parameters specified on that parameter's definition in the cust attrib definition, in the same form as the <keyword_params> above.

Example:

Here is a function that will extract pBlock data from a custom attribute.

mapped fn custAttribute_showPBlockDefs obj =

(

format "%\n" obj.name

for objDef in (custAttributes.getDefs obj) do

(

format "\t%\n" objDef

format "\tname: %\n" objDef.name

format "\tattribute id: %\n" objDef.attribID

format "\tParameter Blocks:"

pbArray = custAttributes.getPBlockDefs objdef

for a = 1 to pbArray.count do

(

itms = pbArray[a]

format "\n\t\tname = %\n" itms[1]

format "\t\tid = %\n" itms[2]

format "\t\towners reference number = %\n" itms[3]

keywordParams = itms[4]

format "\t\tparameter block keywords:\n"

for x = 1 to keywordParams.Count/2 do

(

format "\t\t\t% = %\n" keywordParams[x] keywordParams[x+1]

x = x+1

)

format "\t\tparameters:"

for y = 5 to itms.Count do

(

format "\n\t\t\t#name = %\n" itms[y][1]

for z = 1 to itms[y][2].Count by 2 do

(

format "\t\t\t% = %\n" itms[y][2][z] itms[y][2][z+1]

)

)

)

)

)

custAttributes.getSceneDefs ()

Returns an array of all the attribute definitions in the current scene.

custAttributes.deleteDef <attrib_def>

Deletes the given attribute definiton from the current scene and the current running MAX session. There must be no objects in the scene containing custom attributes added using this definition.

Note: The name can be obtained via the .name property.

Materials and Texture Maps

Scripted custom attributes can be added to any material or texture map. Rollouts defined in these attributes will appear at the end of the Mtl Editor dialog when the material or map containing the custom attributes is selected in the Mtl Editor.

Note:

Typing CAT_Debug = True in the MAXScript listener window will expose the code in the listener when you add a custom attribute through the User Interface.

See also