FAQ

From The Foundry MODO SDK wiki
Revision as of 11:59, 9 May 2013 by Shf (Talk | contribs) (Q: How do I get my C++ implementation from a COM handle?)

Jump to: navigation, search

Frequently Asked Questions: SDK

Q: How do I get a service object?

A: Just declare it, and it is ready to go:

 	CLxUser_SceneService		 srv_scene;
 
 	srv_scene.ItemTypeLookup ("SomeItemType", &notMyType);

The constructor for the C++ wrapper calls lx::GetGlobal() to initialize the interface. There are cases where this can fail, however, so it may be necessary to initialize the service wrapper manually. For example, if you declare your service object as a static.

 	static CLxUser_SceneService	 srv_scene;

In this case the constructor is called as the plug-in code is loaded and before the API has been fully initialized. If you try to use a service that was created too early you will crash with a null 'm_loc' pointer. If that happens you just need to reinitialize the service later with the set() method:

 	srv_scene.set ();

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.

   CLxUser_Scene          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);

There are alternate methods for reading different channel types, and channels can be read given their name rather than index.

   ival = chan_read.IValue (item, LXsICHAN_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:

   CLxUser_ChannelRead     chan_read;
   CLxUser_Matrix          xfrm;

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

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.

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

Q: How do I read a string channel?

A: use the ChannelRead Interface String() method!

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

	unsigned		 index;
	CLxUser_Scene		 scene;
	CLxUser_ChannelRead	 rchan;
	CLxUser_Mesh		 umesh;

	if (LXx_OK(item.ChannelLookup (LXsICHAN_MESH_MESH, &index))) {
		item.GetContext (scene);
		scene.GetChannels (rchan, LXs_ACTIONLAYER_EDIT); // this version is crucial here!!!
		if (rchan.Object (itm, index, umesh))
			np = umesh.NPoints ();
	}

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:

 	unsigned		 index;
	CLxUser_Scene		 scene;
	CLxUser_ChannelRead	 rchan;
	CLxUser_MeshFilter	 mfilt;
	CLxUser_Mesh		 umesh;

	if (LXx_OK(item.ChannelLookup (LXsICHAN_MESH_MESH, &index))) {
		scene.from (item);
		scene.GetChannels (rchan, 0.0); // read the deformed mesh at time zero
		if (rchan.Object (itm, index, mfilt)) {
			if (mfilt.GetMesh (umesh))
				np = umesh.NPoints ();
		}
	}

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: 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: What time is it?

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.

CLxUser_SelectionService   selSrv;

time = selSrv.GetTime ();

When evaluating the scene graph, however, time is not universal.

...

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 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 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 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() and check() methods are provided by inheriting from this utility class.

class CCommmandUtility {
   public:
   void        check (LxResult err)
   {
       if (LXx_FAIL (err))
           throw (err);
   }

   void        check (bool ok, LxResult code = LXe_FAILED)
   {
       if (!ok)
           throw (code);
   }

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

   CLxUser_CommandService         srv_cmd;
};

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

A: There's a hidden hVisible channel on locators that takes parent visibility into account. You just need to read that in an evaluated context.

CLxUser_ChannelRead     eval;

eval.from (item, time);
vis = eval.IValue (item, LXsICHAN_LOCATOR_HVISIBLE);

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

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.

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

A: Yes. Yes it is.

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. For vertex selection this is done like this:

   CLxUser_SelectionService        srv_sel;
   CLxUser_VertexPacketTranslation pkt_trans;
   LXtID4                          sel_ID;

   pkt_trans.autoInit ();
   sel_ID = srv_sel.LookupType (LXsSELTYP_VERTEX);

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.

   LXtPointID                      pnt;
   CLxUser_Mesh                    mesh;
   void                           *pkt;

   srv_sel.Clear (sel_ID);

   pkt = pkt_trans.Packet (pnt, NULL, mesh);
   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 create a new XCode project for a modo plugin, from scratch?

A: Firstly, you don't want to be creating a bundle, you want to create a C/C++ Library, set the type to Dynamic.

Then add a new target to the project, of the same type, but this one is a static library. Add the .cpp files in the SDK common directory to this target and set the header search path to look in the SDK header directory.

Then select your plugin target and go to build phases, add the SDK target as a target dependency. Also add the SDK target under Link Binary with libraries.

That should be everything you need to do.

The easiest way to see how to setup the project files is to start with the sample plugins. There are both Xcode and Visual Studio projects included.

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.

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

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

Q: How can I make sure vtx_ReadChannels is called for every change in time

In vtx_LinkChannels, add a time channel:

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

in vtx_ReadChannels that will be available and return the current time:

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

Q: ?

A: