FAQ

From The Foundry MODO SDK wiki
Revision as of 04:59, 1 September 2017 by Farfarer (Talk | contribs) (Frequently Asked Questions)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Frequently Asked Questions

General

Q: How do I determine the current time?

A: Actually this is a fairly complex question. As a user the answer is simple -- the current time is shown on the time slider and you scrub it to change time. But when writing plug-ins you have to realize that nexus takes a much more holistic view of time and simple linear thinking can cause problems.

For commands that perform edits, or items that draw themselves in 3D, the current global time can generally be used. This is maintained as part of the selection system along with all the other user-controlled state that affects UI interaction.

  1. CLxUser_SelectionService  sel_svc;
  2. double                    time = 0.0;
  3.  
  4. time = sel_svc.GetTime ();

  1. lx.service.Selection ().GetTime ()

As part of modifier evaluation however, time is not universal. Modifiers can be evaluated at arbitrary times and should not rely on the time provided by the selection system. Instead, they should allocate time as an input to the modifier, allowing the evaluation time to be determined.

  1.       LxResult
  2. eval_Alloc (
  3.       ILxUnknownID              item_obj,
  4.       unsigned                  index,
  5.       ILxUnknownID              eval_obj,
  6.       void                    **ppvObj)
  7. {
  8.       CLxUser_Evaluation        eval (eval_obj);
  9.  
  10.       // CLxUser_Attributes
  11.       _attr.set (eval_obj);
  12.  
  13.       _time_idx = eval.AddTime ();
  14.  
  15.       return LXe_OK;
  16. }
  17.  
  18.       void
  19. mod_Evaluate ()
  20. {
  21.       double                    time = 0.0;
  22.  
  23.       time = _attr.Float (_time_idx);
  24. }

  1. def eval_Alloc (self, item_obj, index, eval_obj):
  2.       eval = lx.object.Evaluation (eval_obj)
  3.       self.attr = lx.object.Attributes (eval_obj)
  4.  
  5.       self.time_idx = eval.AddTime ()
  6.  
  7. def mod_Evaluate (self):
  8.       time = self._attr.GetFlt (self.time_idx)

Q: How do I know when the current time changes?

A: Getting notified of global state changes is done though a Global Listener Object. This is an object that you create and export to modo, and your methods will be called when events happen. The easiest way to create a one-off object of this type is to use a Singleton Polymorph. The selevent_Time() method will be called with the new time as the user scrubs the timeline.

class CTimeChangeTracker :
		public CLxImpl_SelectionListener,
		public CLxSingletonPolymorph
{
   public:
	LXxSINGLETON_METHOD;

	CTimeChangeTracker ()
	{
		AddInterface (new CLxIfc_SelectionListener<CTimeChangeTracker>);
	}

		void
	selevent_Time (
		double			 time)
					 LXx_OVERRIDE
	{
		current_time = time;
	}
};

Since this is a singleton you'd store it as global state in your plug-in.

static CTimeChangeTracker      *time_tracker = 0;

The first time you need to start tracking time you create the object and register it with the ListenerService. Do not do this in your initialize() function since that may be too soon.

	CLxUser_ListenerService	 ls;

	time_tracker = new CTimeChangeTracker;
	ls.AddListener (*time_tracker);

When you are done tracking time changes you should unregister your listener.

	CLxUser_ListenerService	 ls;

	ls.RemoveListener (*time_tracker);
	delete time_tracker;

Q: How do I write to the log?

A: Writing to the event log viewport can be done by deriving from the CLxLogMessage utility class (or the CLxLuxologyLogMessage class, which just adds a Lux copyright). This is done by objio.cpp to report load warnings and errors. See Writing to the Event Log for more detail.

The spikey tool sample uses a log block to display tool feedback. The current value is formatted into the block and displayed as part of the tool info viewport.

Writing to the debug output on stdout is possible using one of the variants of LogService::DebugOut(), as shown in the Hello World sample. You have to specify a level, and the default level for release builds is 'error' I think. Lower-level messages are filtered out. If you want to see all the debug output, start modo with the "-debug:verbose" command line switch.

Q: How do I localize a path Alias?

A: Parse a string to FileService.ToLocalAlias which contains just the path alias you would like to resolve. The function will return the absolute pathspec in local OS specific format. If the alias could not be found the original string will be returned. You can pass a that contains a path alias and additional sub-folder content or filespec content and the alias portion will be resolved in place. If the Alias is not translated the original input string is returned unaltered.

Eg. Given a alias ArtisticGlassMaterials = H:\modo\content\Assets\Materials\Glass\Artistic

 CLxUser_[[File Service|File_(lx-file.hpp)]]   FileService;
 char buf[1024];
 string alias("ArtisticGlassMaterials:");
 string result = FileService.ToLocalAlias(buf, alias.c_str()); // result & buf will contain 'H:\modo\content\Assets\Materials\Glass\Artistic' or the OS specific variant
 result = FileService.ToLocalAlias(buf, "ArtisticGlassMaterials:scr\test.py"); // result & buf will contain 'H:\modo\content\Assets\Materials\Glass\Artistic\scr\test.py' or the OS specific variant

  FileService = lx.service.File()
  result = FileService.ToLocalAlias("ArtisticGlassMaterials:scr\test.py") //result will contain 'H:\modo\content\Assets\Materials\Glass\Artistic\scr\test.py' or the OS specific variant

Q: How do I remove a listener object in Python?

A listener is an object you export to be called when changes occur. You create the object and add it, and then remove it when you are done listening. This is a bit tricky in Python, however, because the automatic type conversion in the API works against you. For example, let's say you do the obvious thing and try this:

  class MyListener(lxifc.SceneItemListener):
      pass
 
  lSrv = lx.service.Listener()
  listener = MyListener()
 
  lSrv.AddListener(listener)
  lSrv.RemoveListener(listener) # This won't do what you expect.

Your listener object will be added, but trying to remove it will result in a 'not found' exception. (In older versions of MODO this did not generate an error but left your listener object installed.) The problem is that passing a Python object to a method that expects a COM object will do the conversion automatically, but this means that the two functions are called with different COM objects. Because MODO doesn't recognize the second object as being the same as the first, the listener remains installed.

The solution is to pre-allocate the COM version of your object. You can then pass this to both functions and it will be seen as the same object.

  class MyListener(lxifc.SceneItemListener):
      pass
 
  lSrv = lx.service.Listener()
  listener = MyListener()
  com_listener = lx.object.Unknown(listener)
 
  lSrv.AddListener(com_listener)
  lSrv.RemoveListener(com_listener) # This will correctly remove the listener that was previously spawned.

C++

Q: I compiled my 701 project against the 801 SDK. It builds, but functionality is not there. What should I do?

Some of the 701 SDK samples that could be used as a starting point for projects (e.g. samples/texture_test/valtx.cpp) are missing the 'LXxOVERRIDE' behind the method declarations. If you add this behind each method, the compiler will alert you to argument changes. Without it, that won't happen.

By way of example, the vtx_Evaluate() method for CLxImpl_ValueTexture changed in 801 to require two additional arguments :

701 :

vtx_Evaluate (ILxUnknownID vector, LXpTextureOutput *tOut, void *data)

801 :

vtx_Evaluate (ILxUnknownID etor, int *idx, ILxUnknownID vector, LXpTextureOutput *tOut, void *data)

These additional arguments apply to nodal evaluations.

Q: What does cannot allocate an object of abstract type mean?

A: This means you have inherited from a superclass which has pure virtual methods, and you have failed to provide an implementation for one or more of those methods. In the context of the SDK, the most likely cause is that you're using an implementation class with required methods. For example, suppose your package instance inherits from the ChannelModItem Interface. It's not enough to simply inherit from the implementation.

class CInstance
        : public CLxImpl_PackageInstance,
          public CLxImpl_ChannelModItem
{
        ...
};

If you attempt to initialize a polymorph based on this class it will fail with the abstract type error. That's because the cmod_Flags() method is pure virtual and must be implemented by your class. Of course, for the channel modifier to do anything you need a flags method, so this isn't really a hardship. It's just something to be aware of when starting the implementation for your SDK objects.

Python Specific

Q: DLL loading fails when attempting to loading an external Python Package

Only Python DLL's (or .pyd files) build with MS Visual Studio 2010 are compatible with Modo 801/901.

Python will fail with this error otherwise:

  1.     ImportError: DLL load failed: %1 is not a valid Win32 application.

Most often the source code can be found on GitHub to be compiled with MSVC 2010.

COM

Q: How do I get a service object?

A: Services are global objects, so unlike traditional localized COM objects, they don't need to be initialized. You can simply declare them and then start using them.

  1. CLxUser_SceneService      scn_svc;
  2. LXtItemType               mesh_type;
  3.  
  4. scn_svc.ItemTypeLookup (LXsITYPE_MESH, &mesh_type);

  1. scn_svc = lx.service.Scene()
  2.  
  3. mesh_type = scn_svc.ItemTypeLookup (lx.symbol.sITYPE_MESH)

The constructor for the C++ wrapper calls lx::GetGlobal() to initialize the interface. In some instances, such as when a service is created too early, the GetGlobal function call will fail. In these cases, it may be necessary to initialize the service wrapper manually. If you declare your service object as a static variable, and then simply call set() before use to initialize the interface.

static CLxUser_SceneService      scn_svc;
 
scn_svc.set ();

Q: How do I get my C++ implementation from a COM handle?

A: If you have an interface handle and you know the type of the C++ object that's implementing it, you can unwrap the COM object and get at the meaty C++ object inside. If this is one of your servers, then you just have to call lx::CastServer() with the server name:

	CMyClass *
Extract (
 	ILxUnknownID		 from)
{
	CMyClass		*mine;

	lx::CastServer (SERVER_NAME, from, mine);
	return mine;
}

If the COM object comes from a spawner then you need to use the Cast() method on the spawner:

	CMyClass *
Extract (
 	ILxUnknownID		 from)
{
	CLxSpawner<CMyClass>	 spawn ("myClass");

	return spawn.Cast (from);
}

You have to make sure that the interface pointer you have is actually implemented by your class. If you get an ILxItemID pointer, for example, you need to query for one of your specific implemented interfaces in order to be able to get your package instance class.

Q: Is it possible to create COM wrappers for other languages?

A: Yes. Yes it is.

Items

Q: How do I determine the visibility of an item?

A: Item visibility is defined by a visibility channel that can be set by the user. However, other properties, such as the visibility of parents or the visibility settings of the groups the item belongs to, may modify or override that visibility channel. There is a hidden hVisible channel on locator type items that takes these extra properties into account. Reading this channel in an evaluated context will return the true visibility of an item.

  1. CLxUser_ChannelRead       chan_read;
  2. int                       visible = 0;
  3.  
  4. chan_read.from (item, time);
  5.  
  6. visible = eval.IValue (item, LXsICHAN_LOCATOR_HVISIBLE);

  1. scene = item.Context ()
  2.  
  3. chan_read = lx.object.ChannelRead (scene.Channels (None, time))
  4.  
  5. visible = chan_read.Integer (item, item.ChannelLookup (lx.symbol.sICHAN_LOCATOR_VISIBLE))

Q: How do I read server tags on a package?

A: Packages are servers, so you can access them through the HostService Interface as a Factory Object. This function returns the value for any server tag given the server class, the name of the server, and the tag key.

        const char *
ServerTag (
        const char             *className,
        const char             *serverName,
        const char             *tagKey)
{
        CLxUser_HostService     hostSrv;
        CLxUser_Factory         factory;
        const char             *value;

        hostSrv.Lookup (fac, className, serverName);
        if (LXx_OK (fac.InfoTag (tagKey, &value))
                return value;

        return 0;
}

In order to use this function to read server tag for item types (packages), you just need to find the package name from the item type.

        bool
IsMask (
        CLxUser_Item           &item)
{
        CLxUser_SceneService    scnSrv;
        const char             *pkgName;

        scnSrv.ItemTypeName (item.Type (), &pkgName);
        return (ServerTag (LXa_PACKAGE, pkgName, LXsPKG_IS_MASK) != 0);
}

Channels

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

A: 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.

  1.    //CLxUser_Item         item; - given
  2.    CLxUser_Scene          scene;
  3.    CLxUser_ChannelRead    chan_read;
  4.    CLxUser_Locator        locator (item);
  5.  
  6.    LXtMatrix              xfrm;
  7.    LXtVector              pos;
  8.  
  9.    if (scene.from (item))
  10.    {
  11.       if (scene.GetChannels (chan_read, 0.0))
  12.          locator.WorldTransform (chan_read, xfrm, pos);
  13.    }

   #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.

   //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);
         }
      }
   }

   #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)

Q: How do I read an item's channel value?

A: 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.

  1.     CLxUser_Item           item;    // given
  2.     CLxUser_Scene          scene;   // given
  3.     unsigned               index;   // given
  4.     CLxUser_ChannelRead    chan_read;
  5.  
  6.     scene.GetChannels (chan_read, LXs_ACTIONLAYER_EDIT);
  7.     fval = chan_read.FValue (item, index);

  1.     item = lxu.object.Item()   # given
  2.     scene = lxu.object.Scene() # given
  3.     index = int(channelIndex)  # given
  4.  
  5.     chan_read = scene.Channels(lx.symbol.s_ACTIONLAYER_EDIT, 0.0)
  6.     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.

  1.     ival = chan_read.IValue (item, LXsICHAN_TEXTURELAYER_ENABLE);

  1.     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:

  1.     CLxUser_ChannelRead     chan_read;
  2.     CLxUser_Matrix          xfrm;
  3.  
  4.     chan_read.from (item, 0.0);
  5.     chan_read.Object (item, index, xfrm);

  1.     chan_read = scene.Channels(None, 0.0)
  2.     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.

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

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

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

A: 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.

    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

Q: How do I write a value to an item's channel?

A: 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.

	CLxUser_ChannelWrite	 chan;

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

Q: How do I read a gradient channel?

A: 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++.

  1.     CLxUser_GradientFilter	grad_filt;
  2.     CLxUser_Attributes		attr(attr_obj);
  3.  
  4.     double			yAxis, xAxis = 0.0;
  5.     unsigned			channelIndex;
  6.  
  7.     attr.ObjectRO(channelIndex, grad_filt);
  8.     yAxis = grad_filt.Evaluate(xAxis);

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

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

Meshes

Q: How do I get a CLxUser_Mesh from a mesh item?

A: There are two meshes you can get. If you want the base mesh -- the mesh that the user edits -- then you need to use the form of GetChannels which specifies the action layer, and use LXs_ACTIONLAYER_EDIT. This allows you to read the mesh channel from the action directly:

  1.  	unsigned		 index;
  2.  	CLxUser_Scene		 scene;
  3.  	CLxUser_ChannelRead	 rchan;
  4.  	CLxUser_Mesh     	 umesh;
  5.  
  6.  	if (LXx_OK(item.ChannelLookup (LXsICHAN_MESH_MESH, &index))) {
  7.  		item.GetContext (scene);
  8.  		scene.GetChannels (rchan, LXs_ACTIONLAYER_EDIT); // this version is crucial here!!!
  9.  		if (rchan.Object (itm, index, umesh))
  10.  			np = umesh.NPoints ();
  11.  	}

  1.     item = lxu.object.Item()   # given
  2.     scene = item.Context()
  3.     rchan = scene.Channels(lx.symbol.s_ACTIONLAYER_EDIT, 0.0)
  4.     index = item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH)
  5.     meshChanVal = rchan.ValueObj(item, index)
  6.     umesh = lxu.object.Mesh(meshChanVal)
  7.     np = umesh.PointCount()

If you want to access the mesh after deformation, then you want to read from the evaluated mesh channel. This is done by specifying the time at which you want to evaluate. You can then read the channel as a MeshFilter Interface which can be evaluated:

  1.   	unsigned		 index;
  2.  	CLxUser_Scene		 scene;
  3.  	CLxUser_ChannelRead	 rchan;
  4.  	CLxUser_MeshFilter	 mfilt;
  5.  	CLxUser_Mesh		 umesh;
  6.  
  7.  	if (LXx_OK(item.ChannelLookup (LXsICHAN_MESH_MESH, &index))) {
  8.  		scene.from (item);
  9.  		scene.GetChannels (rchan, 0.0); // read the deformed mesh at time zero
  10.  		if (rchan.Object (itm, index, mfilt)) {
  11.  			if (mfilt.GetMesh (umesh))
  12.  				np = umesh.NPoints ();
  13.  		}
  14.  	}

  1.     item = lxu.object.Item()   # given
  2.     scene = item.Context()
  3.     rchan = scene.Channels(None, 0.0)
  4.     index = item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH)
  5.     meshChanVal = rchan.ValueObj(item, index)
  6.     mfilt = lxu.object.MeshFilter(meshChanVal)
  7.     umesh = mfilt.Generate()
  8.     np = umesh.PointCount()

Note that the channel is a Mesh Object in one case and a EvaluationStack Object in the other. You have to know the source of your ChannelRead Object to know which one you will get. Alternately you could query for the different interface types to probe the object as runtime.

The MeshFilter is also what you get from a modifier. You'd specify the mesh channel as an input and store its attribute index. During evaluation you'd read the channel as a MeshFilter:

	CLxUser_MeshFilter	 mfilt;
	CLxUser_Mesh		 mesh;

	if (m_attr.ObjectRO (i_mesh, mfilt)) {
		if (mfilt.GetMesh (mesh))
			np = mesh.NPoints ();
	}

Q: How do I get the bounding box of a mesh with the Python API?

There are a couple of ways to do this, assuming you have the mesh as an item object, one is by using the "Surface" interface:

chan_eval = scene.Channels(None, 0.0)
surfItem = lxu.object.SurfaceItem(item)
surf = surfItem.GetSurface(chan_eval, 0)
bbox = surf.GetBBox()

Or alternatively you can cast the mesh item as a Mesh object and query it's BoundingBox:

chan_read = scene.Channels(lx.symbol.s_ACTIONLAYER_EDIT, lx.service.Selection().GetTime())
mesh_chan = chan_read.ValueObj(mesh, mesh.ChannelLookup(lx.symbol.sICHAN_MESH_MESH))
mesh_obj = lx.object.Mesh(mesh_chan)
bbox = mesh_obj.BoundingBox(lx.symbol.iMARK_ANY)

Q: A mesh object can be retrieved via a layer scan or mesh channel - what method is preferred?

The preferred way of editing a mesh is by using a layer scan. The mesh channel object does not internally store deltas and thus does not support undoing edits. The mesh channel object is rather useful to evaluate a deformed mesh at an arbitrary time from animation.

Q: How do I get the primary mesh layer item?

The best way to get the primary mesh layer item is using the layer service. This will allocate a layer scan for the primary layer, and then lookup the mesh item.

lyr_svc = lx.service.Layer ()
scan = lx.object.LayerScan (lyr_svc.ScanAllocate (lx.symbol.f_LAYERSCAN_PRIMARY))
if scan.Count () > 0:
 	item = scan.MeshItem (0)
scan.Apply ()

Modifiers

Q: My plugin has a modifier and an item instance with an item drawing interface. How can I get the modifier object inside the instance's methods?

A: Read the modifier object from the appropriate item channel, and convert:

	CLxUser_ValueReference   ref;
	LXtObjectID		 obj;

	chan.Object (m_item, "myModifierObjectChannelName", ref);
	ref.GetObject (&obj);

	CLxSpawner<MyModifierClass>	 spawner ("myModSpawnName");
	MyModifierClass		*mod;

	mod = spawner.Cast ((ILxUnknownID)obj);

     // Do stuff to draw modifier here

	lx::ObjRelease (obj);

Q: How can my channel modifier read inputs at different times?

A: This is done by using methods on the Evaluation Interface. You need to cache this interface as part of your member data in your Allocate() method.

        LxResult
CTimeOffset::cmod_Allocate (
        ILxUnknownID		 cmod,
        ILxUnknownID		 eval,
        ILxUnknownID		 item,
        void		       **ppvData)
{
        m_eval.set (eval);
        ...
}

Then in the Evaluate() method you read the channels you want at the current time first (in this case the current time and an offset time value), then set the evaluation for an alternate time and read other inputs.

        LxResult
CTimeOffset::cmod_Evaluate (
        ILxUnknownID		 cmod,
        ILxUnknownID		 attr,
        void			*data)		
{
        CLxLoc_ChannelModifier	 chanMod (cmod);
        double			 time, dt, value;

        chanMod.ReadInputFloat (attr, INDEX_TIME,   &time);
        chanMod.ReadInputFloat (attr, INDEX_OFFSET, &dt);

        if (dt)
                m_eval.SetAlternateTime (time + dt);

        chanMod.ReadInputFloat (attr, INDEX_INPUT, &value);
        ...
}

Q: Can my modifier read from the setup action?

A: Yes. It's the same process as in the previous answer, but using Evaluation::SetAlternateSetup().

        m_eval.SetAlternateSetup ();

If you want to clear the alternate and read from the current time and action again, use ClearAlternate().

        m_eval.ClearAlternate ();

Q: Why won't my channel modifier write a matrix?

If you're trying to write a matrix in a channel modifier, you might try something like this:

        chanMod.WriteOutputVal (attr, 0, (void **)(&outMatrixPtr));
        for (int i = 0; i < 3; i++) {
                 for (int j = 0; j < 3; j++) {
                          (*outMatrixPtr)[i][j] = m[i][j];
                 }
        }

which wouldn't work. For whatever reason, if you want to write to matrix outputs, you need to write to every link individually. So your code would need to look like this:

        chanMod.OutputCount (0, &outCount);
        for (unsigned idx = 0; idx < outCount; idx++) {
                 chanMod.WriteOutputValByIndex (attr, 0, idx, (void **)(&outMatrixPtr));
                          for (int i = 0; i < 3; i++) {
                                   for (int j = 0; j < 3; j++) {
                                            (*outMatrixPtr)[i][j] = m[i][j];
                                   }
                          }
        }

Q: How can I make sure modifier is evaluated for every time change

Time can be allocated as an input for the modifier. As changes to input channels invalidates modifiers and causes them to be re-evaluated, allocating time as an input to the modifier will cause the modifier to be invalidated and re-evaluated whenever time changes.

Allocate time as an input channel:

 CLxUser_Evaluation	 ev (eval);
 idx_currentTime = ev.AddTime();

In the modifier Evaluate function, read the current time.

 CLxUser_Attributes	 at (attr);
 double currentTime = at.Float(idx_currentTime);

Selection

Q: How do I change selections?

A: Changing selection is done using the SelectionService Interface. For each selection type you need its translator and its selection type ID code. In the Python API, the packet translation object doesn't have the autoInit method, but it's easy enough to initialize. You just need to allocate a SelectionType object for the Vertex Translator.

  1.     CLxUser_SelectionService        srv_sel;
  2.     CLxUser_VertexPacketTranslation pkt_trans;
  3.     LXtID4                          sel_ID;
  4.  
  5.     pkt_trans.autoInit ();
  6.     sel_ID = srv_sel.LookupType (LXsSELTYP_VERTEX);

  1.     srv_sel = lx.service.Selection()
  2.     selTypObj = srv_sel.Allocate(lx.symbol.sSELTYP_VERTEX)
  3.     pkt_trans = lx.object.VertexPacketTranslation(selTypObj)


In order to operate on selections you need to create a packet from your data, and use the packet pointer to query or alter the selection. For example, to select a single vertex you would clear the current selection, create a packet for the vertex using the vertex ID and mesh item, then select the packet.

  1.     LXtPointID                      pnt;
  2.     CLxUser_Mesh                    mesh;
  3.     void                           *pkt;
  4.  
  5.     srv_sel.Clear (sel_ID);
  6.  
  7.     pkt = pkt_trans.Packet (pnt, NULL, mesh);
  8.     srv_sel.Select (sel_ID, pkt);

  1.     sel_ID = srv_sel.LookupType(lx.symbol.sSELTYP_VERTEX)
  2.     srv_sel.Clear(sel_ID)
  3.  
  4.     pkt = pkt_trans.Packet(pnt, 0, mesh)
  5.     srv_sel.Select(sel_ID, pkt)

Changing selection is undoable, so these can only be called while executing an undoable command.

Q: How do I know what item a selection belongs to?

  1. CLxUser_SelectionService         sel_svc;
  2. CLxUser_PolygonPacketTranslation polygon_pkt_trans;
  3. CLxUser_Item                     item;
  4. LXtID4                           sel_type_polygon;
  5. void                            *selection_pkt;
  6.  
  7. # Set up a polygon selection packet translator, which lets us read information about a polygon selection packet.
  8. polygon_pkt_trans.autoInit ();
  9. sel_type_polygon = sel_svc.LookupType (LXsSELTYP_POLYGON);
  10.  
  11. # Grab the polygon selection packet by index (0 in this case, first selected polygon).
  12. selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0);
  13.  
  14. # This will get the item this polygon belongs to.
  15. polygon_pkt_trans.Item(selection_pkt, &item);

  1. sel_svc = lx.service.Selection ()
  2.  
  3. sel_type_polygon = sel_svc.LookupType (lx.symbol.sSELTYP_POLYGON)
  4.  
  5. # Set up a polygon selection packet translator, which lets us read information about a polygon selection packet.
  6. polygon_pkt_trans = lx.object.PolygonPacketTranslation (sel_svc.Allocate(lx.symbol.sSELTYP_POLYGON))
  7.  
  8. # Grab the polygon selection packet by index (0 in this case, first selected polygon).
  9. selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0)
  10.  
  11. # This will get the item this polygon belongs to.
  12. item = polygon_pkt_trans.Item(selection_pkt)

Commands

Q: How do I make my command button update when the enable state changes?

A: You need a notifier. Command notifiers send change flags on specific events to indicate that some aspect of the command's state may have changed. Flags can indicate the enable/disable state, the label, the value, or the datatype.

  • Notifiers can be added to a basic command by implementing basic_Notifier() which returns the name and arguments for each notifier by index.
  • Common notifiers for selection changes, mesh edits, etc, can be found in the notifier docs.
  • Changes to plug-in state can trigger notifications by declaring a notifier server. These are created by inheriting from CLxCommandNotifier.

Q: How do I make my command's boolean argument appear as a toggle button?

A: You can set the default style of a command's boolean argument using UIHints, done by implementing arg_UIHints() in Python.

By default, boolean type arguments will appear as a checkbox when exposed in the UI, but you can specify the hint for the boolean argument's index to default to a toggle button like this.

  1. def arg_UIHints (self, index, hints):
  2.     if index == 0:
  3.         hints.BooleanStyle (lx.symbol.iBOOLEANSTYLE_BUTTON)

  1. ...

Q: How do I open a file dialog from my command?

A: Normally this is done in your Interact() method, something like this:

	void
CMyLoadCommand::cmd_Interact ()
{
	/*
	 * Open the dialog using the "dialog.*" sub-commands. Works here
	 * because they are non-model, non-undoable commands.
	 */
	fire ("dialog.setup fileOpen");
	fire ("dialog.title {Load Animation}");
	fire ("dialog.fileTypeCustom {Quicktime Movie} {*.mov;*.mp4} mov");
	fire ("dialog.open");

	/*
	 * Query the result, getting a list of filenames.
	 */
	CLxUser_Command		 resCmd;
	CLxUser_ValueArray	 va;
	LXtObjectID		 obj;
	unsigned int		 n;

	check ( srv_cmd.NewCommand (resCmd, "dialog.result") );
	check ( srv_cmd.QueryIndex (resCmd, 0, va), LXe_FAILED );

	/*
	 * Although it's a list, there's only one filename (since this was
	 * a single-file dialog).  We'll set our filename argument to the
	 * first one in the list.
	 */
	n = va.Count ();
	if (!n)
		return;

	std::string		 filename;

	check ( va.String (0, filename) );
	check ( attr_SetString (0, filename.c_str ()) );
}

The fire() method is provided by inheriting from this utility class.

class CCommmandUtility {
   public:
   void        fire (const char *cmd)
   {
       check ( srv_cmd.ExecuteArgString (-1, LXiCTAG_NULL, cmd) );
   }

   CLxUser_CommandService         srv_cmd;
};

The check() functions are defined in the lx_err namespace.

Images

Q: How can I access image layers?

Plug-in source file

  1. CLxUser_ChannelRead read;
  2. read.from (item, 0.0);
  3.  
  4. // Read object from channel and cast it to a ImageFilter inteface
  5. CLxUser_ImageFilter filter;
  6. read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, filter);
  7.  
  8. CLxUser_ImageFilterMetrics myMetrics;
  9. read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, myMetrics);
  10.  
  11. // This will fill the metrics structure so that we can read the image dimensions from it
  12. LXtImageMetrics metrics;
  13. CLxUser_ImageFilterMetrics myMetrics (filter);
  14. myMetrics.Generate (&metrics);
  15.  
  16. // This returns the image from the filter
  17. CLxUser_Image image;
  18. filter.Generate (metrics.maxRes[0], metrics.maxRes[1], 0, image);
  19.  
  20. // Read an RGBA pixel
  21. LXtPixelFormat format = image.Format();
  22. if (format == LXiIMV_RGBA)
  23. {
  24.         unsigned char pixel[4];
  25.         LxResult result = image.GetPixel (0, 0, format, pixel);
  26. }

  1. if item.type == lx.symbol.sITYPE_IMAGELAYER:
  2.  
  3.     imgFilter = lx.object.ImageFilter( item.channel('imageStack').get() )
  4.     image = imgFilter.Generate(64,64, None)
  5.  
  6.     if image.Format() == lx.symbol.iIMV_RGBA:
  7.         storage = lx.object.storage('b', 4)
  8.         image.GetPixel( 0,0, lx.symbol.iIMV_RGBA, storage)
  9.         print 'RGBA values: ', [storage[i] for i in xrange(4)]

Configs and Kits

Q: Why are my Kit's Python files ignored when running under linux?

Linux is case sensitive. Ensure the names and extensions of the config files in your kit are lowercase.

Changing Scene State from Listeners

Q: When is it safe to make changes to the scene from inside a listener?

Listeners allow you to respond to events within Modo, such as selection changes, channel edits, time changes, etc...

However, making changes to the scene in response to those events can cause potential issues depending on the current undo state (e.g. the event might be part of the user undoing something and generally you don't want to make changes to the scene in response to that).

To find out the undo state, you can query it from the Undo Service and take appropriate steps depending on the result.

  1. CLxUser_UndoService undo_svc;
  2. unsigned int undoState = undo_svc.State();
  3. // undoState will be one of:
  4. // LXiUNDO_INVALID
  5. // LXiUNDO_ACTIVE
  6. // LXiUNDO_SUSPEND

  1. undoState = lx.service.Undo().State()
  2. # undoState will be one of:
  3. # lx.symbol.iUNDO_INVALID
  4. # lx.symbol.iUNDO_ACTIVE
  5. # lx.symbol.iUNDO_SUSPEND

  • INVALID - The undo state is invalid. It is not safe to make changes to the scene.
  • ACTIVE - The undo system is recording undo steps for the changes being made. It is safe to make changes to the scene and they will be undoable.
  • SUSPEND - The undo system is not recording undo steps for the changes being made. Generally this happens during events like scene loading. It is safe to make changes to the scene, however they will not be undoable.