Difference between revisions of "FAQ"
Ivo.grigull (Talk | contribs) |
Chris Hague (Talk | contribs) |
||
(34 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
− | == Frequently Asked Questions | + | == 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. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_SelectionService sel_svc; | ||
+ | double time = 0.0; | ||
+ | |||
+ | time = sel_svc.GetTime (); | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python" line> | ||
+ | lx.service.Selection ().GetTime () | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | LxResult | ||
+ | eval_Alloc ( | ||
+ | ILxUnknownID item_obj, | ||
+ | unsigned index, | ||
+ | ILxUnknownID eval_obj, | ||
+ | void **ppvObj) | ||
+ | { | ||
+ | CLxUser_Evaluation eval (eval_obj); | ||
+ | |||
+ | // CLxUser_Attributes | ||
+ | _attr.set (eval_obj); | ||
+ | |||
+ | _time_idx = eval.AddTime (); | ||
+ | |||
+ | return LXe_OK; | ||
+ | } | ||
+ | |||
+ | void | ||
+ | mod_Evaluate () | ||
+ | { | ||
+ | double time = 0.0; | ||
+ | |||
+ | time = _attr.Float (_time_idx); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python" line> | ||
+ | def eval_Alloc (self, item_obj, index, eval_obj): | ||
+ | eval = lx.object.Evaluation (eval_obj) | ||
+ | self.attr = lx.object.Attributes (eval_obj) | ||
+ | |||
+ | self.time_idx = eval.AddTime () | ||
+ | |||
+ | def mod_Evaluate (self): | ||
+ | time = self._attr.GetFlt (self.time_idx) | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | ==== 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 Interface|SelectionListener]], | ||
+ | public CLxSingletonPolymorph | ||
+ | { | ||
+ | public: | ||
+ | LXxSINGLETON_METHOD; | ||
+ | |||
+ | CTimeChangeTracker () | ||
+ | { | ||
+ | AddInterface (new CLxIfc_[[SelectionListener Interface|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 Interface|ListenerService]] ls; | ||
+ | |||
+ | time_tracker = new CTimeChangeTracker; | ||
+ | ls.AddListener (*time_tracker); | ||
+ | |||
+ | When you are done tracking time changes you should unregister your listener. | ||
+ | |||
+ | CLxUser_[[ListenerService Interface|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 | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | ==== 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: | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | class MyListener(lxifc.SceneItemListener): | ||
+ | pass | ||
+ | |||
+ | lSrv = lx.service.Listener() | ||
+ | listener = MyListener() | ||
+ | |||
+ | lSrv.AddListener(listener) | ||
+ | lSrv.RemoveListener(listener) # This won't do what you expect. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | 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. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === 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 : | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | vtx_Evaluate (ILxUnknownID vector, LXpTextureOutput *tOut, void *data) | ||
+ | </syntaxhighlight> | ||
+ | 801 : | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | vtx_Evaluate (ILxUnknownID etor, int *idx, ILxUnknownID vector, LXpTextureOutput *tOut, void *data) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <syntaxhighlight lang="python" line> | ||
+ | ImportError: DLL load failed: %1 is not a valid Win32 application. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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? ==== | ==== Q: How do I get a service object? ==== | ||
− | A: | + | 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. |
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
− | + | CLxUser_SceneService scn_svc; | |
+ | LXtItemType mesh_type; | ||
− | + | scn_svc.ItemTypeLookup (LXsITYPE_MESH, &mesh_type); | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
− | + | scn_svc = lx.service.Scene() | |
+ | |||
+ | mesh_type = scn_svc.ItemTypeLookup (lx.symbol.sITYPE_MESH) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
+ | |||
+ | The constructor for the C++ wrapper calls [[GetGlobal (index) | ''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. | ||
− | |||
<syntaxhighlight> | <syntaxhighlight> | ||
− | + | static CLxUser_SceneService scn_svc; | |
+ | |||
+ | scn_svc.set (); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ||
− | <syntaxhighlight> | + | ==== 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 [[Using a Spawner | 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. [[Language Wrappers|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. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_ChannelRead chan_read; | ||
+ | int visible = 0; | ||
+ | |||
+ | chan_read.from (item, time); | ||
+ | |||
+ | visible = eval.IValue (item, LXsICHAN_LOCATOR_HVISIBLE); | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python" line> | ||
+ | scene = item.Context () | ||
+ | |||
+ | chan_read = lx.object.ChannelRead (scene.Channels (None, time)) | ||
+ | |||
+ | visible = chan_read.Integer (item, item.ChannelLookup (lx.symbol.sICHAN_LOCATOR_VISIBLE)) | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | //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); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="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] | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | //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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="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) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | </tabber> | ||
==== Q: How do I read an item's channel value? ==== | ==== Q: How do I read an item's channel value? ==== | ||
Line 33: | Line 434: | ||
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. | 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. | It should be noted that in python, a time needs to be provided even if it's not necessarily used. | ||
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
CLxUser_Item item; // given | CLxUser_Item item; // given | ||
Line 44: | Line 446: | ||
fval = chan_read.FValue (item, index); | fval = chan_read.FValue (item, index); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
item = lxu.object.Item() # given | item = lxu.object.Item() # given | ||
Line 54: | Line 457: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
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. | 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. | ||
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
ival = chan_read.IValue (item, LXsICHAN_TEXTURELAYER_ENABLE); | ival = chan_read.IValue (item, LXsICHAN_TEXTURELAYER_ENABLE); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
ival = chan_read.Value(item, lx.symbol.sICHAN_TEXTURELAYER_ENABLE) | ival = chan_read.Value(item, lx.symbol.sICHAN_TEXTURELAYER_ENABLE) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
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: | 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: | ||
− | + | <tabber> | |
− | + | C++= | |
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
CLxUser_ChannelRead chan_read; | CLxUser_ChannelRead chan_read; | ||
Line 81: | Line 484: | ||
chan_read.Object (item, index, xfrm); | chan_read.Object (item, index, xfrm); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
chan_read = scene.Channels(None, 0.0) | chan_read = scene.Channels(None, 0.0) | ||
xfrm = chan_read.ValueObj(item, index) | xfrm = chan_read.ValueObj(item, index) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
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. | 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. | ||
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
scene.from (item); | scene.from (item); | ||
index = item.ChannelIndex ("ChannelName"); | index = item.ChannelIndex ("ChannelName"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
scene = item.Context() | scene = item.Context() | ||
index = item.ChannelLookup("ChannelName") | index = item.ChannelLookup("ChannelName") | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
==== Q: How do I set the default value of a string channel? ==== | ==== Q: How do I set the default value of a string channel? ==== | ||
Line 134: | Line 535: | ||
chan.from (item); | chan.from (item); | ||
chan.Set (item, channel, value); | 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++. | ||
+ | |||
+ | <tabber> | ||
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_[[ILxGradientFilter_(index)|GradientFilter]] grad_filt; | ||
+ | CLxUser_[[ILxAttributes_(index)|Attributes]] attr(attr_obj); | ||
+ | |||
+ | double yAxis, xAxis = 0.0; | ||
+ | unsigned channelIndex; | ||
+ | |||
+ | attr.ObjectRO(channelIndex, grad_filt); | ||
+ | yAxis = grad_filt.Evaluate(xAxis); | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python" line> | ||
+ | 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) | ||
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
+ | |||
+ | 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? ==== | ==== Q: How do I get a CLxUser_Mesh from a mesh item? ==== | ||
Line 139: | Line 573: | ||
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: | 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: | ||
− | + | <tabber> | |
− | + | C++= | |
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
Line 155: | Line 589: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
Line 167: | Line 602: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
− | + | ||
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: | 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: | ||
− | + | <tabber> | |
− | + | C++= | |
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
unsigned index; | unsigned index; | ||
Line 191: | Line 624: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
Line 204: | Line 638: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ||
− | + | ||
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. | 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. | ||
Line 220: | Line 652: | ||
} | } | ||
− | ==== Q: | + | ==== 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: | |
− | + | <syntaxhighlight lang="python"> | |
− | + | chan_eval = scene.Channels(None, 0.0) | |
− | + | surfItem = lxu.object.SurfaceItem(item) | |
− | + | surf = surfItem.GetSurface(chan_eval, 0) | |
− | + | bbox = surf.GetBBox() | |
− | + | </syntaxhighlight> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | Or alternatively you can cast the mesh item as a Mesh object and query it's BoundingBox: | |
− | + | <syntaxhighlight lang="python"> | |
− | + | 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) | |
− | + | ||
− | <syntaxhighlight lang=" | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
+ | ==== 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. | |
− | + | <syntaxhighlight lang="python"> | |
+ | 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 () | ||
+ | </syntaxhighlight> | ||
− | + | === 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: | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | A: | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | CLxUser_[[ValueReference Interface|ValueReference]] ref; | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | CLxUser_[[ | + | |
− | + | ||
LXtObjectID obj; | 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); | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | lx:: | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
==== Q: How can my channel modifier read inputs at different times? ==== | ==== Q: How can my channel modifier read inputs at different times? ==== | ||
Line 485: | Line 780: | ||
} | } | ||
− | ==== Q: How | + | ==== 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? ==== | ==== 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. | 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. | ||
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
CLxUser_SelectionService srv_sel; | CLxUser_SelectionService srv_sel; | ||
Line 549: | Line 810: | ||
sel_ID = srv_sel.LookupType (LXsSELTYP_VERTEX); | sel_ID = srv_sel.LookupType (LXsSELTYP_VERTEX); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
srv_sel = lx.service.Selection() | srv_sel = lx.service.Selection() | ||
Line 555: | Line 817: | ||
pkt_trans = lx.object.VertexPacketTranslation(selTypObj) | pkt_trans = lx.object.VertexPacketTranslation(selTypObj) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
+ | |||
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. | 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. | ||
− | + | ||
− | + | <tabber> | |
+ | C++= | ||
<syntaxhighlight lang="cpp" line> | <syntaxhighlight lang="cpp" line> | ||
LXtPointID pnt; | LXtPointID pnt; | ||
Line 570: | Line 834: | ||
srv_sel.Select (sel_ID, pkt); | srv_sel.Select (sel_ID, pkt); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
sel_ID = srv_sel.LookupType(lx.symbol.sSELTYP_VERTEX) | sel_ID = srv_sel.LookupType(lx.symbol.sSELTYP_VERTEX) | ||
Line 578: | Line 843: | ||
srv_sel.Select(sel_ID, pkt) | srv_sel.Select(sel_ID, pkt) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
Changing selection is undoable, so these can only be called while executing an undoable command. | Changing selection is undoable, so these can only be called while executing an undoable command. | ||
− | ==== Q: How do I | + | ==== Q: How do I know what item a selection belongs to? ==== |
− | + | <tabber> | |
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_SelectionService sel_svc; | ||
+ | CLxUser_PolygonPacketTranslation polygon_pkt_trans; | ||
+ | CLxUser_Item item; | ||
+ | LXtID4 sel_type_polygon; | ||
+ | void *selection_pkt; | ||
− | + | # Set up a polygon selection packet translator, which lets us read information about a polygon selection packet. | |
+ | polygon_pkt_trans.autoInit (); | ||
+ | sel_type_polygon = sel_svc.LookupType (LXsSELTYP_POLYGON); | ||
− | + | # Grab the polygon selection packet by index (0 in this case, first selected polygon). | |
+ | selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0); | ||
− | + | # This will get the item this polygon belongs to. | |
− | + | polygon_pkt_trans.Item(selection_pkt, &item); | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
+ | Python= | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
− | + | sel_svc = lx.service.Selection () | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | sel_type_polygon = sel_svc.LookupType (lx.symbol.sSELTYP_POLYGON) | |
− | = | + | # Set up a polygon selection packet translator, which lets us read information about a polygon selection packet. |
+ | polygon_pkt_trans = lx.object.PolygonPacketTranslation (sel_svc.Allocate(lx.symbol.sSELTYP_POLYGON)) | ||
− | + | # Grab the polygon selection packet by index (0 in this case, first selected polygon). | |
+ | selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0) | ||
− | + | # This will get the item this polygon belongs to. | |
− | + | item = polygon_pkt_trans.Item(selection_pkt) | |
+ | </syntaxhighlight> | ||
+ | </tabber> | ||
− | + | === 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 [[notify (lx-notify.hpp)|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 [[Command_(lx-command.hpp)#User_Interface:_ILxUIHints|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. | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | <tabber> | |
− | + | Python= | |
− | + | <syntaxhighlight lang="python" line> | |
− | + | def arg_UIHints (self, index, hints): | |
− | + | if index == 0: | |
− | + | hints.BooleanStyle (lx.symbol.iBOOLEANSTYLE_BUTTON) | |
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | | | + | |-| |
− | <syntaxhighlight lang=" | + | C++= |
− | + | <syntaxhighlight lang="cpp" line> | |
− | + | ... | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | </tabber> | |
− | + | ==== 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 Interface|Command]] resCmd; | ||
+ | CLxUser_[[ValueArray Interface|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 Interface|CommandService]] srv_cmd; | |
− | + | }; | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | | | + | |
− | + | ||
− | + | ||
− | + | The check() functions are defined in the lx_err namespace. | |
− | + | ||
− | + | === Images === | |
− | + | ==== Q: How can I access image layers? ==== | |
− | + | [[Snippet:Accessing_image_layers|Plug-in source file]] | |
− | + | ||
− | + | ||
− | == | + | <tabber> |
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_ChannelRead read; | ||
+ | read.from (item, 0.0); | ||
− | + | // Read object from channel and cast it to a ImageFilter inteface | |
− | + | CLxUser_ImageFilter filter; | |
+ | read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, filter); | ||
− | + | CLxUser_ImageFilterMetrics myMetrics; | |
− | + | read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, myMetrics); | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | // This will fill the metrics structure so that we can read the image dimensions from it | |
+ | LXtImageMetrics metrics; | ||
+ | CLxUser_ImageFilterMetrics myMetrics (filter); | ||
+ | myMetrics.Generate (&metrics); | ||
− | + | // This returns the image from the filter | |
+ | CLxUser_Image image; | ||
+ | filter.Generate (metrics.maxRes[0], metrics.maxRes[1], 0, image); | ||
− | <syntaxhighlight lang="python"> | + | // Read an RGBA pixel |
− | + | LXtPixelFormat format = image.Format(); | |
− | + | if (format == LXiIMV_RGBA) | |
− | + | { | |
− | + | unsigned char pixel[4]; | |
− | + | LxResult result = image.GetPixel (0, 0, format, pixel); | |
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |-| | ||
+ | Python= | ||
+ | <syntaxhighlight lang="python" line> | ||
+ | if item.type == lx.symbol.sITYPE_IMAGELAYER: | ||
− | + | imgFilter = lx.object.ImageFilter( item.channel('imageStack').get() ) | |
− | + | image = imgFilter.Generate(64,64, None) | |
+ | |||
+ | if image.Format() == lx.symbol.iIMV_RGBA: | ||
+ | storage = lx.object.storage('b', 4) | ||
+ | image.GetPixel( 0,0, lx.symbol.iIMV_RGBA, storage) | ||
+ | print 'RGBA values: ', [storage[i] for i in xrange(4)] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | </tabber> | ||
− | + | === 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: | + | ==== 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 (lx-undo.hpp)|Undo Service]] and take appropriate steps depending on the result. | |
− | <syntaxhighlight lang="cpp"> | + | |
− | + | <tabber> | |
+ | C++= | ||
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxUser_UndoService undo_svc; | ||
+ | unsigned int undoState = undo_svc.State(); | ||
+ | // undoState will be one of: | ||
+ | // LXiUNDO_INVALID | ||
+ | // LXiUNDO_ACTIVE | ||
+ | // LXiUNDO_SUSPEND | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | |-| | |
− | <syntaxhighlight lang=" | + | Python= |
− | + | <syntaxhighlight lang="python" line> | |
+ | undoState = lx.service.Undo().State() | ||
+ | # undoState will be one of: | ||
+ | # lx.symbol.iUNDO_INVALID | ||
+ | # lx.symbol.iUNDO_ACTIVE | ||
+ | # lx.symbol.iUNDO_SUSPEND | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | </tabber> | ||
− | + | * '''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. | ||
− | == | + | == More Rarely Asked Questions == |
− | + | === General === | |
− | ==== Q: | + | ==== Q: How do use and read selection choice from a custom popup? ==== |
− | + | If you want to create a popup selection choice an argument for a command, the easiest option is to use a LXtTextValueHintcreate and set that hint for an integer argument using dyna_SetHint. | |
− | = | + | <syntaxhighlight lang="cpp" line> |
− | + | static LXtTextValueHint argChoices[] = | |
− | + | { | |
− | + | 0, "choiceA", | |
− | + | 1, "choiceB", | |
− | + | 2, "choiceC", | |
− | + | -1, NULL | |
− | + | } | |
− | + | MyCommand::MyCommand () | |
+ | { | ||
+ | dyna_Add ("choices", LXsTYPE_INTEGER); | ||
+ | dyna_SetHint (argChoices); | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | If you're list is dynamic, Text Value Hints might not be an option. Instead you can use CLxUIValue or CLxDynamicUIValue inside of a overridden atrui_UIValue function to create your list of choices. | |
+ | <syntaxhighlight lang="cpp" line> | ||
+ | CLxDynamicUIValue MyCommand::atrui_UIValue (unsigned int argIdx) | ||
+ | { | ||
+ | CLxUIValue *pop = NULL; | ||
+ | CLxUser_Scene scene; | ||
+ | CLxUser_Item item; | ||
+ | const char *name; | ||
+ | CLxSceneSelection scnSel; | ||
+ | unsigned i, n; | ||
− | + | if (argIdx == 0) { | |
− | + | scnSel.Get(scene); | |
− | + | pop = new CLxUIValue(); | |
− | + | pop->popup_add ("choiceA"); | |
− | + | pop->popup_add ("choiceB"); | |
+ | scene.ItemCount(cit_mesh, &n); | ||
+ | for (i=0; i<n; i++) { | ||
+ | scene.ItemByIndex(cit_mesh, i, item); | ||
+ | item.UniqueName(&name); | ||
+ | pop->popup_add(item.IdentPtr(), name); | ||
+ | } | ||
+ | return pop; | ||
+ | } | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | It can be tricky to work out how to read these values later on, however, so I wanted to write it down. | |
− | + | <syntaxhighlight lang="cpp" line> | |
+ | // Read which choice was selected | ||
+ | int choiceIdx; | ||
+ | choiceIdx = dyna_Int(0); | ||
− | + | // Create a hint and set it using the UIValueHints method | |
− | + | CLxLoc_UIValueHints hnts; | |
− | + | LXtObjectID obj; | |
+ | atrui_UIValueHints(0, &obj); | ||
+ | hnts.take(obj); | ||
− | + | // Read the user name at the chosen index (or any other property) | |
− | + | stMeshIdent = hnts.PopUserName(mIdx); | |
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− |
Latest revision as of 20:32, 2 August 2018
Contents
- 1 Frequently Asked Questions
- 1.1 General
- 1.2 C++
- 1.3 Python Specific
- 1.4 COM
- 1.5 Items
- 1.6 Channels
- 1.7 Meshes
- 1.8 Modifiers
- 1.8.1 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?
- 1.8.2 Q: How can my channel modifier read inputs at different times?
- 1.8.3 Q: Can my modifier read from the setup action?
- 1.8.4 Q: Why won't my channel modifier write a matrix?
- 1.8.5 Q: How can I make sure modifier is evaluated for every time change
- 1.9 Selection
- 1.10 Commands
- 1.11 Images
- 1.12 Configs and Kits
- 1.13 Changing Scene State from Listeners
- 2 More Rarely Asked Questions
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.
CLxUser_SelectionService sel_svc;
double time = 0.0;
time = sel_svc.GetTime ();
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.
LxResult
eval_Alloc (
ILxUnknownID item_obj,
unsigned index,
ILxUnknownID eval_obj,
void **ppvObj)
{
CLxUser_Evaluation eval (eval_obj);
// CLxUser_Attributes
_attr.set (eval_obj);
_time_idx = eval.AddTime ();
return LXe_OK;
}
void
mod_Evaluate ()
{
double time = 0.0;
time = _attr.Float (_time_idx);
}
def eval_Alloc (self, item_obj, index, eval_obj):
eval = lx.object.Evaluation (eval_obj)
self.attr = lx.object.Attributes (eval_obj)
self.time_idx = eval.AddTime ()
def mod_Evaluate (self):
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:
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.
CLxUser_SceneService scn_svc;
LXtItemType mesh_type;
scn_svc.ItemTypeLookup (LXsITYPE_MESH, &mesh_type);
scn_svc = lx.service.Scene()
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.
CLxUser_ChannelRead chan_read;
int visible = 0;
chan_read.from (item, time);
visible = eval.IValue (item, LXsICHAN_LOCATOR_HVISIBLE);
scene = item.Context ()
chan_read = lx.object.ChannelRead (scene.Channels (None, time))
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.
//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);
}
#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.
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);
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.
ival = chan_read.IValue (item, LXsICHAN_TEXTURELAYER_ENABLE);
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:
CLxUser_ChannelRead chan_read;
CLxUser_Matrix xfrm;
chan_read.from (item, 0.0);
chan_read.Object (item, index, xfrm);
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.
scene.from (item);
index = item.ChannelIndex ("ChannelName");
scene = item.Context()
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++.
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);
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.
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:
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 ();
}
item = lxu.object.Item() # given
scene = item.Context()
rchan = scene.Channels(lx.symbol.s_ACTIONLAYER_EDIT, 0.0)
index = item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH)
meshChanVal = rchan.ValueObj(item, index)
umesh = lxu.object.Mesh(meshChanVal)
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:
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 ();
}
}
item = lxu.object.Item() # given
scene = item.Context()
rchan = scene.Channels(None, 0.0)
index = item.ChannelLookup(lx.symbol.sICHAN_MESH_MESH)
meshChanVal = rchan.ValueObj(item, index)
mfilt = lxu.object.MeshFilter(meshChanVal)
umesh = mfilt.Generate()
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.
CLxUser_SelectionService srv_sel;
CLxUser_VertexPacketTranslation pkt_trans;
LXtID4 sel_ID;
pkt_trans.autoInit ();
sel_ID = srv_sel.LookupType (LXsSELTYP_VERTEX);
srv_sel = lx.service.Selection()
selTypObj = srv_sel.Allocate(lx.symbol.sSELTYP_VERTEX)
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.
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);
sel_ID = srv_sel.LookupType(lx.symbol.sSELTYP_VERTEX)
srv_sel.Clear(sel_ID)
pkt = pkt_trans.Packet(pnt, 0, 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 know what item a selection belongs to?
CLxUser_SelectionService sel_svc;
CLxUser_PolygonPacketTranslation polygon_pkt_trans;
CLxUser_Item item;
LXtID4 sel_type_polygon;
void *selection_pkt;
# Set up a polygon selection packet translator, which lets us read information about a polygon selection packet.
polygon_pkt_trans.autoInit ();
sel_type_polygon = sel_svc.LookupType (LXsSELTYP_POLYGON);
# Grab the polygon selection packet by index (0 in this case, first selected polygon).
selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0);
# This will get the item this polygon belongs to.
polygon_pkt_trans.Item(selection_pkt, &item);
sel_svc = lx.service.Selection ()
sel_type_polygon = sel_svc.LookupType (lx.symbol.sSELTYP_POLYGON)
# Set up a polygon selection packet translator, which lets us read information about a polygon selection packet.
polygon_pkt_trans = lx.object.PolygonPacketTranslation (sel_svc.Allocate(lx.symbol.sSELTYP_POLYGON))
# Grab the polygon selection packet by index (0 in this case, first selected polygon).
selection_pkt = sel_svc.ByIndex(sel_type_polygon, 0)
# This will get the item this polygon belongs to.
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.
def arg_UIHints (self, index, hints):
if index == 0:
hints.BooleanStyle (lx.symbol.iBOOLEANSTYLE_BUTTON)
...
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?
CLxUser_ChannelRead read;
read.from (item, 0.0);
// Read object from channel and cast it to a ImageFilter inteface
CLxUser_ImageFilter filter;
read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, filter);
CLxUser_ImageFilterMetrics myMetrics;
read.Object (item, LXsICHAN_VIDEOCLIP_IMAGESTACK, myMetrics);
// This will fill the metrics structure so that we can read the image dimensions from it
LXtImageMetrics metrics;
CLxUser_ImageFilterMetrics myMetrics (filter);
myMetrics.Generate (&metrics);
// This returns the image from the filter
CLxUser_Image image;
filter.Generate (metrics.maxRes[0], metrics.maxRes[1], 0, image);
// Read an RGBA pixel
LXtPixelFormat format = image.Format();
if (format == LXiIMV_RGBA)
{
unsigned char pixel[4];
LxResult result = image.GetPixel (0, 0, format, pixel);
}
if item.type == lx.symbol.sITYPE_IMAGELAYER:
imgFilter = lx.object.ImageFilter( item.channel('imageStack').get() )
image = imgFilter.Generate(64,64, None)
if image.Format() == lx.symbol.iIMV_RGBA:
storage = lx.object.storage('b', 4)
image.GetPixel( 0,0, lx.symbol.iIMV_RGBA, storage)
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.
CLxUser_UndoService undo_svc;
unsigned int undoState = undo_svc.State();
// undoState will be one of:
// LXiUNDO_INVALID
// LXiUNDO_ACTIVE
// LXiUNDO_SUSPEND
undoState = lx.service.Undo().State()
# undoState will be one of:
# lx.symbol.iUNDO_INVALID
# lx.symbol.iUNDO_ACTIVE
# 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.
More Rarely Asked Questions
General
Q: How do use and read selection choice from a custom popup?
If you want to create a popup selection choice an argument for a command, the easiest option is to use a LXtTextValueHintcreate and set that hint for an integer argument using dyna_SetHint.
static LXtTextValueHint argChoices[] =
{
0, "choiceA",
1, "choiceB",
2, "choiceC",
-1, NULL
}
MyCommand::MyCommand ()
{
dyna_Add ("choices", LXsTYPE_INTEGER);
dyna_SetHint (argChoices);
}
If you're list is dynamic, Text Value Hints might not be an option. Instead you can use CLxUIValue or CLxDynamicUIValue inside of a overridden atrui_UIValue function to create your list of choices.
CLxDynamicUIValue MyCommand::atrui_UIValue (unsigned int argIdx)
{
CLxUIValue *pop = NULL;
CLxUser_Scene scene;
CLxUser_Item item;
const char *name;
CLxSceneSelection scnSel;
unsigned i, n;
if (argIdx == 0) {
scnSel.Get(scene);
pop = new CLxUIValue();
pop->popup_add ("choiceA");
pop->popup_add ("choiceB");
scene.ItemCount(cit_mesh, &n);
for (i=0; i<n; i++) {
scene.ItemByIndex(cit_mesh, i, item);
item.UniqueName(&name);
pop->popup_add(item.IdentPtr(), name);
}
return pop;
}
}
It can be tricky to work out how to read these values later on, however, so I wanted to write it down.
// Read which choice was selected
int choiceIdx;
choiceIdx = dyna_Int(0);
// Create a hint and set it using the UIValueHints method
CLxLoc_UIValueHints hnts;
LXtObjectID obj;
atrui_UIValueHints(0, &obj);
hnts.take(obj);
// Read the user name at the chosen index (or any other property)
stMeshIdent = hnts.PopUserName(mIdx);