Package: Server basics

A Package is a plug-in server that defines a collection of channels for an item, or a new item type. The capabilities of these new packages or types are determined by the interfaces presented by the package instance.

Overview

Items are made from a collection of Package Object which provides the implementation for the item’s features. For example, interfaces allow an item to draw in the 3D view, or act as a channel modifier, or act as a 3D surface. Some can even be combined in the same item.

Headers

  • ‘’’package (lx-package.hpp)’’’
    • ‘’CLxImpl_Package’’

    • ‘’CLxUser_PackageInstance’’

  • ‘’’idef (lxidef.h)’’’
    • ‘’LXsITYPE_LOCATOR’’

  • LXsPKG_SUPERTYPE – the name of the item type to act as super-type (e.g. “LXsITYPE_LOCATOR”) or “.” for a root item type.

  • LXsPKG_GRAPHS

  • LXsPKG_CREATECMD – LXs_PKG_NODIRECTCREATE

  • LXsPKG_CONVERTCMD – LXs_PKG_DIRECTCONVERTOK

  • LXsPKG_IS_MASK

  • LXsPKG_SHADER_CONTEXT

  • LXsPKG_CREATE_INDIRECT

For a plug-in package we need to define two classes, one for the package and one for the instance.

Package Instance

The instance class at minimum derives from CLxImpl_PackageInstance, and exports the ILxPackageInstance interface. It can also derive from other possible Package Interfaces.

PackageInstance::Initialize()

The Initialize() method is called when the instance is associated with the item. It gets the Item Object]] which it can store, plus the [[PackageInstance Object of the super-type.

1
2
3
4
5
6
7
8
         LxResult
 pins_Initialize (
         ILxUnknownID            item,
         ILxUnknownID            super)        LXx_OVERRIDE
 {
         m_item.set (item);
         return LXe_OK;
 }

PackageInstance::Cleanup()

Cleanup() is called before the item is destroyed.

1
2
3
4
5
         void
 pins_Cleanup (void)                           LXx_OVERRIDE
 {
         m_item.clear ();
 }

Package

The plug-in package server class derives from CLxImpl_Package and exports the ILxPackage interface. Since the package will need to spawn the instances, the spawner will be a member variable.

1
2
3
4
5
 class CMyPackage :
         public CLxImpl_Package
 {
     public:
         CLxSpawner<CInstance>   inst_spawn;
1
2
3
         CMyPackage () : inst_spawn ("myInstSpawner") {}
         ...
 };

Package::SetupChannels()

The SetupChannels() method is called the first time and instance of this package is to be created, allowing the package to define the set of channels that it wants. Channels are given by name and type, and optional settings may be applied to each one.

1
2
3
4
5
         LxResult
 pkg_SetupChannels (
         ILxUnknownID            addChan)     LXx_OVERRIDE
 {
         CLxUser_AddChannel      ac (addChan);
1
2
         ac.NewChannel  ("range",       LXsTYPE_DISTANCE);
         ac.SetDefault  (1.0, 0);
1
2
3
4
5
         ac.NewChannel  ("mode",        LXsTYPE_INTEGER);
         ac.SetDefault  (0.0, MODE_LINEAR);
         ac.SetHint     (hint_Mode);
         return LXe_OK;
 }

Package::TestInterface()

The TestInterface() method is required. It just calls the same method on the spawner.

1
2
3
4
5
6
         LxResult
 pkg_TestInterface (
         const LXtGUID          *guid)        LXx_OVERRIDE
 {
         return inst_spawn.TestInterfaceRC (guid);
 }

Package::Attach()

Attach() is called to create the instance for a new item. We use the spawner to create a new default instance which can then be customized for this object. We have to wait until the instance’s own Initialize() method to know the item that’s being created.

1
2
3
4
5
         LxResult
 pkg_Attach (
         void                  **ppvObj)      LXx_OVERRIDE
 {
         CMyInstance            *inst = inst_spawn.Alloc (ppvObj);
1
2
3
         ''< intialize inst >''
         return LXe_OK;
 }

Adding Interfaces

Additional capability, such as ViewItem3D: Package basics, are enabled by presenting more interfaces on the instance. The package instance for a minimal package will be declared using code similar to this.

1
2
3
4
5
6
7
8
 class CMyPInstance :
         public CLxImpl_[[PackageInstance Interface|PackageInstance]]
 {
     public:
                 static void
         initialize ()
         {
                CLxGenericPolymorph     *srv;
1
2
3
4
                srv = new CLxPolymorph<CMyInstance>;
                srv->AddInterface (new CLxIfc_[[PackageInstance Interface|PackageInstance]]<CInstance>);
                lx::AddSpawner ("myInstance", srv);
         }
1
2
         ...
 };

Adding a new interface is just a matter of adding a new parent implementation class, and presenting a new interface.

1
2
3
4
5
6
7
8
9
 class CMyPInstance :
         public CLxImpl_[[PackageInstance Interface|PackageInstance]],
         ''public CLxImpl_[[ViewItem3D Interface|ViewItem3D]]''
 {
     public:
                 static void
         initialize ()
         {
                CLxGenericPolymorph     *srv;
1
2
3
4
5
                srv = new CLxPolymorph<CMyPInstance>;
                srv->AddInterface (new CLxIfc_[[PackageInstance Interface|PackageInstance]]<CMyPInstance>);
                ''srv->AddInterface (new CLxIfc_[[ViewItem3D Interface|ViewItem3D]]<CMyPInstance>);''
                lx::AddSpawner ("myInstance", srv);
         }
1
2
         ...
 };

Resources

A package with a supertype tag will show up in modo as an item type that can be created and selected, and its channels can be viewed in the item list. This can be sufficient for initial testings, but to really integrate the item into the UI it requires resources to define user names, tooltips, forms, and icons.

Item Help

Information about item types and their channels are stored in a CommandHelp/Item hash. The channels are keyed by internal name and allow for properties like the user name and tooltip. These are used to display the channel list and property sheets. Messages can also be stored in the item help info.

1
    <atom type="CommandHelp">
1
2
        <hash type="Item" key="myItemType@en_US">
            <atom type="UserName">My Item Type</atom>
1
2
3
4
5
6
7
8
            <hash type="Channel" key="enable">
                <atom type="UserName">Enable</atom>
                <atom type="Tooltip">When enabled this item performs its function.</atom>
            </hash>
            <hash type="Channel" key="color">
                <atom type="UserName">Color</atom>
                <atom type="Tooltip">Sets the display color of the item.</atom>
            </hash>
1
2
            <hash type="Message" key="isDisabled">This item is disabled.</hash>
        </hash>
1
    </atom>

Properties

The properties are displayed based on a selection filter. This filter tests true when an item of your type is selected. Filters are best treated as black boxes; simply replace myItemType and ‘’My Item Type’’ with those of your item type or package.

1
    <atom type="Filters">
1
2
3
4
5
6
7
        <hash type="Preset" key="myItemType.item:filterPreset">
            <atom type="Name">My Item Type</atom>
            <atom type="Enable">1</atom>
            <list type="Node">1 .group 0 &quot;&quot;</list>
            <list type="Node">1 itemtype 0 1 &quot;myItemType&quot;</list>
            <list type="Node">-1 .endgroup </list>
        </hash>
1
    </atom>

The properties form (also known as an attribute sheet) is defined by an Attribute/Sheet hash. This sheet is for an item type derived from ‘’locator’’, and inserts itself into the general item properties via a Form Categories and Groups determining the order of its tab relative to the others in that category. For the most part sheets like this consist of item.channel commands for the item’s channels. Note that the filter is also set to the myItemType filter that was defined above.

1
    <atom type="Attributes">
1
2
3
4
        <hash type="Sheet" key="myItemType.item:sheet">
            <atom type="Label">My Item Type</atom>
            <atom type="Filter">myItemType.item:filterPreset</atom>
            <atom type="Group">itemprops/render</atom>
1
2
3
            <hash type="InCategory" key="itemprops:general#head">
                <atom type="Ordinal">129</atom>
            </hash>
1
2
            <list type="Control" val="ref 11145884454:sheet"></list>
            <list type="Control" val="ref 80746857872:sheet"></list>
1
2
3
4
            <list type="Control" val="div ">
                <atom type="Label">My Item Type</atom>
                <atom type="Alignment">full</atom>
            </list>
1
2
3
            <list type="Control" val="cmd item.channel myItemType$enable ?"/>
            <list type="Control" val="cmd item.channel myItemType$color ?"/>
        </hash>
1
    </atom>

Icons & Categories

Defining Item Categories determine where your item type shows up in various “add item” popups in the UI.

1
    <atom type="Categories">
1
2
3
        <hash type="Category" key="itemtype:locator">
            <hash type="C" key="myItemType">locators</hash>
        </hash>
1
    </atom>

Icon Resources are define in the UIElements root-level atom. You specify a resource image with a unique key string, and then reference that to pick the icons out of it, usually based on a grid.

1
    <atom type="UIElements">
1
        <hash type="Image" key="my_items">my_items.tga</hash>
1
2
3
4
        <hash type="Icon" key="item.myItemType">
            <atom type="Source">my_items</atom>
            <atom type="Grid">0 0 13 13</atom>
        </hash>
1
    </atom>