Channels

How do I read the transform channels for a locator type item?

Unlike other 3D applications, modo allows you create multiple transforms (position, rotation and scale) on a single item, and allows you to reorder them to control how those transforms are applied. This results in an extremely flexible rigging workflow, allowing you to create complex transforms on a single item, without requiring a hierarchy of multiple items.

These transforms are stored as a separate item, connected to the locator type item that they want to manipulate, via a graph. This can be somewhat confusing, as you may try and search for the transform channels on the locator item and be unable to find them.

There are two ways to access and manipulate these transforms. Firstly, you can use the Locator Interface, this is an interface on the locator type item and has various methods for manipulating transforms. For example, if you want to get the main transform item for either rotation, scale or translation, you use the GetTransformItem method and pass it the type of transform you want. You can then read the channels on that item directly. There are also some helpful methods for getting and setting both the world and local transforms.

C++

//CLxUser_Item         item; - given
CLxUser_Scene          scene;
CLxUser_ChannelRead    chan_read;
CLxUser_Locator        locator (item);

LXtMatrix              xfrm;
LXtVector              pos;

if (scene.from (item))
{
        if (scene.GetChannels (chan_read, 0.0))
                locator.WorldTransform (chan_read, xfrm, pos);
}

Python

#item = lx.object.Item () # Given
locator = lx.object.Locator (item)
scene = item.Context ()

chan_read = lx.object.ChannelRead (scene.Channels (None, 0.0))

transforms = locator.WorldTransform (chan_read)

xfrm = transforms[0]
pos = transforms[1]

The alternative method for getting transform items, is to walk the graph and find the transform item directly. This is a little more in-depth, but allows greater control over the values returned, and also allows you to manipulate the transform items themselves.

The transform items are linked to the locator item they provide transforms for, in the XfrmCore graph, with the transform items connecting into the locator item. A single locator type item could potentially have multiple transforms connected through this graph. The order they are connected is the reverse order of the order they appear in the channel list inside of modo.

C++

//CLxUser_Item         item; - given
CLxUser_Scene          scene;
CLxUser_Item           transform;
CLxUser_ItemGraph      graph;

unsigned               count = 0;

if (scene.from (item))
{
       if (scene.GetGraph (LXsGRAPH_XFRMCORE, graph))
       {
              count = graph.Reverse (item);

              for (unsigned i=0; i<count; i++)
              {
                     graph.Reverse (item, i, transform);
              }
       }
}

Python

#item = lx.object.Item () # Given

scene = item.Context ()
graph = lx.object.ItemGraph (scene.GraphLookup (lx.symbol.sGRAPH_XFRMCORE))

count = graph.RevCount (item)

for i in range (count):
        transform = graph.RevByIndex (item, i)

How do I read an item’s channel value?

It depends on the context. Mostly you use a ChannelRead Interface, unless you are in a modifier of some kind, in which case a different API should be used.

The normal case also has two forms. The first allows you to access channel values stored in the base (edit) action. Given a scene, an item in the scene and the channel index, you can get a channel read object from the scene and use that to access the value of channels in the action. It should be noted that in python, a time needs to be provided even if it’s not necessarily used.

C++

CLxUser_Item           item;    // given
CLxUser_Scene          scene;   // given
unsigned               index;   // given
CLxUser_ChannelRead    chan_read;

scene.GetChannels (chan_read, LXs_ACTIONLAYER_EDIT);
fval = chan_read.FValue (item, index);

Python

item = lxu.object.Item()   # given
scene = lxu.object.Scene() # given
index = int(channelIndex)  # given

chan_read = scene.Channels(lx.symbol.s_ACTIONLAYER_EDIT, 0.0)
fval = chan_read.Double(item, index)

There are alternate methods for reading different channel types. Channels can be read given their name rather than index, as well. In python, this is built into the Value user method, which can read float, integer, or string channels without needing to specify the type in advance.

C++

ival = chan_read.IValue (item, LXsICHAN_TEXTURELAYER_ENABLE);

Python

ival = chan_read.Value(item, lx.symbol.sICHAN_TEXTURELAYER_ENABLE)

The other form is for reading evaluated channels, like the various matricies which are computed from the transform channels. In that case you provide the time for the evaluation rather than the action name:

C++

CLxUser_ChannelRead     chan_read;
CLxUser_Matrix          xfrm;

chan_read.from (item, 0.0);
chan_read.Object (item, index, xfrm);

Python

chan_read = scene.Channels(None, 0.0)
xfrm = chan_read.ValueObj(item, index)

If all you have is an item you can get a scene from that, and if you want to keep the index for faster access you can look it up from the item.

C++

scene.from (item);
index = item.ChannelIndex ("ChannelName");

Python

scene = item.Context()
index = item.ChannelLookup("ChannelName")

How do I set the default value of a string channel?

After adding the channel with the AddChannel Interface, you can obtain the default value object and call the methods of its Value Interface to set the string. The storage type of the channel has to be set to string.

C++

CLxUser_AddChannel  ac; // given
LXtObjectID         obj;
CLxUser_Value       val;

ac.SetStorage (LXsTYPE_STRING);
ac.SetDefaultObj (&obj);
val.take (obj);
val.SetString ("Default Value");

The somewhat misleadingly named SetDefaultObj() returns an object on which you can call its Value Interface methods to set the default value, in this case a string.

Example item plugin: Setting a string channels default value

How do I write a value to an item’s channel?

Get a ChannelWrite Object, initialize it and set the channel’s value. The channel argument can be the channel index or the channel name: C++ will pick the right method for you. value can be integer, float or string.

C++

CLxUser_ChannelWrite     chan;

chan.from (item);
chan.Set (item, channel, value);

How do I read a gradient channel?

A gradient channel is read using the ILxGradientFilter interface.

The evalulate method on the GradientFilter interface takes an input value (the X axis) and returns the corresponding output value or Y axis. In Python, gradient channels can be read into Value Objects, which are then read through a Gradient Filter Object just like C++.

C++

CLxUser_GradientFilter  grad_filt;
CLxUser_Attributes              attr(attr_obj);

double                  yAxis, xAxis = 0.0;
unsigned                        channelIndex;

attr.ObjectRO(channelIndex, grad_filt);
yAxis = grad_filt.Evaluate(xAxis);

Python

scene = meshItem.Context()
chanRead = scene.Channels(None, 0.0)
valueObj = chanRead.ValueObj(meshItem, meshItem.ChannelLookup('radGrad'))
grad_filt = lx.object.GradientFilter(valueObj)
yAxis = grad_filt.Evaluate(xAxis)

yAxis now holds the gradient channel value at the position on the gradient defined by xAxis.