Command VertValue

Command VertValue is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

Code Walkthrough

VertValue.cpp

Class Declarations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class CPersistData {
    public:
        CLxUser_PersistentEntry  mainHash;
        CLxUser_PersistentEntry  atom;
        CLxUser_PersistentEntry  hash;
        CLxUser_PersistentEntry  list;

        CLxUser_Attributes       aVal;
        CLxUser_Attributes       hVal;
        CLxUser_Attributes       lVal;

        void             GenIndex ();
        void             Peek ();
        void             Poke (int comp, double value, const char *name);
        char             index_buf[4];
};

This class is largely a shell to store persistent data entries. All of the relevant structures we already have access to thanks to our inclusion of lx_persist.hpp and lx_value.hpp

1
2
3
4
5
 class CPersistVisitor : public CLxImpl_AbstractVisitor
 {
    public:
        LxResult         Evaluate ();
 };

The structure of persistent data is set up using a vistor. This allows the system to bracket the setup with its own state management. The structure is created is as follows:

root TestData

1
2
3
4
          hash MainHash
            atom FloatAtom %f
            hash IntHash %d
            list StringList %s

In order to turn out class into a visitor, though, we need to inherit from ./Visitor_(lx-visitor.hpp), which has all the necessary methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class CVertexSelectionVisitor : public CLxImpl_AbstractVisitor
{
    public:
        virtual void StartMesh  (class CVertexCompCommand &)  {}
        virtual bool Vertex     (class CVertexCompCommand &) = 0;

        class CVertexCompCommand        *m_cvc;
        void init (class CVertexCompCommand *cvc)
        {
                m_cvc = cvc;
        }

        LxResult Evaluate ()
        {
                if (Vertex (*m_cvc))
                        return LXe_ABORT;
                else
                        return LXe_OK;
        }
};

This command will operate over selected vertices, so we have a specialized version of a visitor for processing them. To turn this class into a visitor we will inherit from CLxImpl_AbstractVisitor. StartMesh() is called when we enter a new mesh (if multiple meshes are selected) and Vertex() is called for each vertex. Vertex() can return true to halt the enumeration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CVertexCompCommand : public CLxBasicCommand
{
    public:
         CVertexCompCommand ();

        int             basic_CmdFlags  () LXx_OVERRIDE;
        bool            basic_Notifier  (int index, string &name, string &args) LXx_OVERRIDE;
        const char *    basic_ArgType   (unsigned int index) LXx_OVERRIDE;
        bool            basic_Enable    (CLxUser_Message &msg) LXx_OVERRIDE;

        void            cmd_Execute     (unsigned int flags) LXx_OVERRIDE;
        LxResult        cmd_Query       (unsigned int index, ILxUnknownID vaQuery) LXx_OVERRIDE;

        bool            GetVMap         ();
        void            Enumerate       (CVertexSelectionVisitor &, bool edit = false);

        CLxUser_MeshService              srv_mesh;
        CLxUser_LayerService             srv_lay;
        CLxUser_SelectionService         srv_sel;
        CLxUser_VMapPacketTranslation    pkt_vmap;
        LXtID4                           selID_vmap, selID_vert;
        LXtMarkMode                      select_mode;

        const char                      *cm_name;
        LXtID4                           cm_type;
        unsigned                         cm_dim;
        unsigned                         cm_change;

        CLxUser_Mesh                     cm_mesh;
        CLxUser_Point                    cm_point;
        LXtMeshMapID                     cm_mapID;
};

This class will be performing actions on Modo, so we need to make it a basic command; to make it a ./Command_(lx-command.hpp)#Command_Objects we inherit from CLxBasicCommand.

./Initialize_(index)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 void initialize ()
 {
        CLxGenericPolymorph     *srv;

        srv = new CLxPolymorph<CVertexCompCommand>;
        srv->AddInterface (new CLxIfc_Command     <CVertexCompCommand>);
        srv->AddInterface (new CLxIfc_Attributes  <CVertexCompCommand>);
        srv->AddInterface (new CLxIfc_AttributesUI<CVertexCompCommand>);
        lx::AddServer ("vertex.componentValue", srv);

        init_InstanceSource ();
 }

This initialize function indicates that we will be exporting one server, dependent on the CVertexCompCommand, that will have all the interfaces corresponding to the classes that CVertexCompCommand inherited from. The name of this server will be vertex.componentValue.

Helper Functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        static void
 Persist_Setup (void)
 {
        if (pd)
                return;

        pd = new CPersistData;

        CLxUser_PersistenceService srv;
        CPersistVisitor          vis;

        srv.ConfigureVis ("TestData", &vis);
 }

This function performs setup on a number of key services; this happens once per session.

1
2
3
4
5
 void cleanup ()
 {
        if (pd)
                delete pd;
 }

This function cleans up the persistent data set up by Persist_Setup.

Implementations

1
2
3
 #define ARGi_COMP               0
 #define ARGi_VALUE         1
 #define ARGi_TYPE               2

These are command argument indices that are used in the constructor below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
CVertexCompCommand::CVertexCompCommand ()
{
        CLxUser_SelectionType            styp;

        Persist_Setup ();

        dyna_Add ("component", LXsTYPE_INTEGER);
        dyna_Add ("value",     LXsTYPE_FLOAT);
        dyna_Add ("type",      LXsTYPE_STRING);

        basic_SetFlags (ARGi_VALUE, LXfCMDARG_QUERY | LXfCMDARG_VARIABLE);
        basic_SetFlags (ARGi_TYPE,  LXfCMDARG_OPTIONAL);

        selID_vmap = srv_sel.LookupType ("vmap");
        srv_sel.GetImplementation (selID_vmap, styp);
        pkt_vmap.set (styp);

        selID_vert = srv_sel.LookupType ("vertex");

        select_mode = srv_mesh.SetMode ("select");
}

Setting up our command requires first initializing the arguments. They are added to the dynamic attributes and their flags are set. We also look up some selection types and the mode for selected elements.

1
2
3
4
5
      int
CVertexCompCommand::basic_CmdFlags ()
{
        return LXfCMD_MODEL | LXfCMD_UNDO;
}

Basic_CmdFlags indicates what type of command it is. MODEL means that it affects the data model of the scene, in this case the meshes, and UNDO means that it supports undo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
        bool
CVertexCompCommand::basic_Notifier (
        int                      index,
        string                  &name,
        string                  &args)
{
        if (index == 0) {
                name = "select.event";
                args = "vmap +vdt";             // VALUE+DISABLE+TYPE on vmap selection

        } else if (index == 1) {
                name = "select.event";
                args = "vertex exist+d";        // DISABLE on vertex selection existence

        } else if (index == 2) {
                name = "select.event";
                args = "vertex +v";             // VALUE on vertex selection change

        } else if (index == 3) {
                name = "meshes.event";
                args = "+ouma +v";              // VALUE on position(o), UV map(u), morph map(m), and other map(a) edits

        } else
                return false;

        return true;
}

Notifiers are used to signal changes to a command’s state to any client that may be using it. For example a command in a form may be disabled. If something changes in the system that changes the disable state, we need a notifier so that the form will be triggered to update. This method on the basic command is called with sequentially higher index values until it returns false. As long as it returns true it will add more notifiers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
     bool
CVertexCompCommand::GetVMap ()
{
        void                    *pkt;

        /*
         * If the 'type' argument is unset, we just take the most recent vmap
         * selection and read it's name and type.
         */
        if (!dyna_IsSet (ARGi_TYPE)) {
                pkt = srv_sel.Recent (selID_vmap);
                if (!pkt)
                        return false;

                pkt_vmap.Name (pkt, &cm_name);
                pkt_vmap.Type (pkt, &cm_type);
                return true;
        }

        /*
         * If the type is set, we decode it and then walk the selection to find
         * a map that fits the request.
         */
        string                   typeStr;
        LXtID4                   type;
        unsigned                 i, n;

        attr_GetString (ARGi_TYPE, typeStr);
        if (LXx_FAIL (srv_mesh.VMapLookupType (typeStr.c_str (), &type)))
                return false;

        n = srv_sel.Count (selID_vmap);
        for (i = 0; i < n; i++) {
                pkt = srv_sel.ByIndex (selID_vmap, i);

                pkt_vmap.Type (pkt, &cm_type);
                if (cm_type != type)
                        continue;

                pkt_vmap.Name (pkt, &cm_name);
                return true;
        }

        return false;
}

This method selects a map based on the state of the command. If it returns true then ‘cm_name’ and ‘cm_type’ have been successfully set.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        void
CVertexCompCommand::Enumerate (
        CVertexSelectionVisitor &vsv,
        bool                     edit)
{
        CLxUser_LayerScan        scan;
        unsigned                 i, n;

        if (!srv_sel.Count (selID_vert))
                return;

        if (!GetVMap ())
                return;

        srv_mesh.VMapDimension (cm_type, &cm_dim);
        if (cm_type == LXi_VMAP_OBJECTPOS)
                cm_change = LXf_MESHEDIT_POSITION;
        else if (cm_type == LXi_VMAP_TEXTUREUV)
                cm_change = LXf_MESHEDIT_MAP_UV;
        else if (cm_type == LXi_VMAP_MORPH || cm_type == LXi_VMAP_SPOT)
                cm_change = LXf_MESHEDIT_MAP_MORPH;
        else
                cm_change = LXf_MESHEDIT_MAP_OTHER;

        n = LXf_LAYERSCAN_ACTIVE | LXf_LAYERSCAN_MARKVERTS;
        if (edit)
                n |= LXf_LAYERSCAN_WRITEMESH;

        if (!srv_lay.BeginScan (n, scan))
                return;

        vsv.init (this);

        n = scan.NumLayers ();
        for (i = 0; i < n; i++) {
                if (edit)
                        scan.EditMeshByIndex (i, cm_mesh);
                else
                        scan.BaseMeshByIndex (i, cm_mesh);

                CLxUser_MeshMap mmap (cm_mesh);
                mmap.SelectByName (cm_type, cm_name);
                cm_mapID = mmap.ID ();

                vsv.StartMesh (*this);

                cm_point.fromMeshObj (cm_mesh);
                cm_point.Enum (&vsv, select_mode);

                // the POINTS flag shouldn't be needed (bug 22347)
                //
                if (edit)
                        scan.SetMeshChange (i, LXf_MESHEDIT_POINTS | cm_change);
        }

        if (edit)
                scan.Apply ();
}

This is a core utility function that enumerates selected vertices with our custom visitor. This uses a layer scan object created by the layer service. Since this obeys the normal rule that if no points are seelcted all are selected, we test the selection explicitly so we do nothing when there is no explicit selection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
        bool
CVertexCompCommand::basic_Enable (
        CLxUser_Message         &msg)
{
        CEnableVisitor           vis;
        int                      comp;

        vis.any = false;
        Enumerate (vis);

        if (!vis.any)
                return false;

        if (!dyna_IsSet (ARGi_COMP))
                return true;

        attr_GetInt (ARGi_COMP, &comp);
        return (comp >= 0 && comp < static_cast<int>(cm_dim));
}

We’re disabled if there is nothing to operate on. Otherwise we want to be enabled if the ‘component’ arg is unset (to open a full dialog). If set, it has to be smaller than the map dimension.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
       const char *
CVertexCompCommand::basic_ArgType (
        unsigned int             index)
{
        if (!GetVMap ())
                return LXsTYPE_FLOAT;

        if (cm_type == LXi_VMAP_TEXTUREUV)
                return LXsTYPE_UVCOORD;

        if (cm_type == LXi_VMAP_WEIGHT)
                return LXsTYPE_PERCENT;

        if (cm_type == LXi_VMAP_MORPH || cm_type == LXi_VMAP_SPOT)
                return LXsTYPE_DISTANCE;

        if (cm_type == LXi_VMAP_RGBA  || cm_type == LXi_VMAP_RGB)
                return LXsTYPE_COLOR1;

        return LXsTYPE_FLOAT;
}

This method will be called to get the type of the variable argument type, which in our case is the ‘type’ argument. ‘index’ will be ARGi_VALUE, so we don’t really have to test that. We pick a datatype appropriate for the map type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        LxResult
CVertexCompCommand::cmd_Query (
        unsigned int             index,
        ILxUnknownID             vaQuery)
{
        CQueryVisitor            vis;

        vis.va.set (vaQuery);
        attr_GetInt (ARGi_COMP, &vis.comp);

        Enumerate (vis);
        return LXe_OK;
}

This command creates an array that holds objects of value ARGi_COMP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
        void
CVertexCompCommand::cmd_Execute (
        unsigned int             flags)
{
        CApplyVisitor            vis;

        attr_GetInt (ARGi_COMP,  &vis.comp);
        attr_GetFlt (ARGi_VALUE, &vis.value);

        Enumerate (vis, true);

        basic_Message().SetCode (LXe_OK);

        /*
         * Test persistent data by recording executions.
         */
        pd->GenIndex ();
        pd->Peek ();
        pd->Poke (vis.comp, vis.value, cm_name);
}

The cmd_Execute function is executed when the plugin is run. We retrieve the values given to ARGi_COMP and ARGi_VALUE. We then test persistent data by recording executions.

instsrc.cpp

Class Declarations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class CItemVisitor
{
    public:
        virtual bool    Item (CLxUser_Item &item) = 0;
};
class CItemSelectionTracker :
                public CItemVisitor,
                public CLxImpl_SelectionListener,
                public CLxSingletonPolymorph
{
    public:
        CLxUser_SceneService             srv_scene;
        CLxUser_SelectionService         srv_sel;
        CLxUser_ItemPacketTranslation    pkt_item;
        LXtID4                           selID_item;
        bool                             is_valid;
        unsigned                         use_count;
        std::set<LXtItemType>            cur_types;

        LXxSINGLETON_METHOD;

        CItemSelectionTracker ()
        {
                is_valid  = false;
                use_count = 1;

                AddInterface (new CLxIfc_SelectionListener<CItemSelectionTracker>);

                selID_item = srv_sel.LookupType ("item");
                pkt_item.autoInit ();
        }

                void
        selevent_Add (
                LXtID4                   type,
                unsigned int             subtype)
                                         LXx_OVERRIDE
        {
                if (type == selID_item)
                        is_valid = false;
        }

                void
        selevent_Remove (
                LXtID4                   type,
                unsigned int             subtype)
                                         LXx_OVERRIDE
        {
                if (type == selID_item)
                        is_valid = false;
        }

                void
        Enumerate (
                CItemVisitor            &vis)
        {
                CLxUser_Item             item;
                LXtScanInfoID            scan;
                void                    *pkt;

                scan = 0;
                while (scan = srv_sel.ScanLoopCurrent (scan, selID_item, &pkt)) {
                        pkt_item.GetItem (pkt, item);
                        if (vis.Item (item))
                                return;
                }
        }

                void
        ValidateTypeSet ()
        {
                cur_types.clear ();
                Enumerate (*this);
        }

                bool
        Item (
                CLxUser_Item            &item)
        {
                cur_types.insert (item.Type ());
                return false;
        }

                bool
        AllowType (
                LXtItemType              type)
        {
                std::set<LXtItemType>::iterator  sit;

                if (!is_valid)
                        ValidateTypeSet ();

                for (sit = cur_types.begin(); sit != cur_types.end(); sit++)
                        if (srv_scene.ItemTypeTest (*sit, type) == LXe_TRUE)
                                return true;

                return false;
        }
};

The selection tracker class keeps track of when item selection changes and takes care of building a list of unique item types for the current state. It’s also in charge of enumeration, which is why we have a vistor class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 class CInstSourceCommand : public CLxBasicCommand
 {
    public:
         CInstSourceCommand ();
        ~CInstSourceCommand ();

        int             basic_CmdFlags  () LXx_OVERRIDE;
        bool            basic_Notifier  (int index, std::string &name, std::string &args) LXx_OVERRIDE;
        bool            basic_Enable    (CLxUser_Message &msg) LXx_OVERRIDE;

        CLxDynamicUIValue *
                        atrui_UIValue   (unsigned int index);

        void            cmd_Execute     (unsigned int flags) LXx_OVERRIDE;
        LxResult        cmd_Query       (unsigned int index, ILxUnknownID vaQuery) LXx_OVERRIDE;
 };

The ‘instance.source’ command has the usual collection of basic methods, plus a method for customizing argument UI.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 class CEnableItemVisitor : public CItemVisitor
 {
    public:
        bool             any;

        bool Item (CLxUser_Item &item)
        {
                any = true;
                return true;
        }
 };

Enable – test if there’s anything selected.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class CQueryItemVisitor : public CItemVisitor
{
    public:
        CLxUser_ValueArray       va;

        bool Item (CLxUser_Item &item)
        {
                CLxUser_Value            val;
                CLxUser_ValueReference   ref;
                LXtObjectID              src;

                va.AddEmpty (val);
                ref.set (val);
                if (LXx_OK (item.Source (&src)))
                        ref.SetObject ((ILxUnknownID) src);

                return false;
        }
};

Query – return a list of all selected item’s source items. This is a value reference datatype so it allows for ‘none’.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 class CItemPopup : public CLxDynamicUIValue
 {
    public:
        unsigned        Flags ()
                        LXx_OVERRIDE
        {
                return LXfVALHINT_ITEMS | LXfVALHINT_ITEMS_NONE;
        }

        bool            ItemTest (CLxUser_Item &item)
                        LXx_OVERRIDE
        {
                return sT->AllowType (item.Type ());
        }
 };

Customize the item popup. We use the selection tracker to tell us if the item should be part of the item popup. Also, we want a ‘none’ item choice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CExecItemVisitor : public CItemVisitor
{
    public:
        CLxUser_Item             set_to;
        const char              *set_id;
        int                      n_total, n_fail;

        bool Item (CLxUser_Item &item)
        {
                LxResult         rc;
                const char      *id;

                n_total ++;
                rc = LXe_FAILED;

                if (set_to.test ()) {
                        if (set_to.IsA (item.Type ())) {
                                item.Ident (&id);
                                if (id != set_id)
                                        rc = item.SetSource (set_to);
                        }
                } else
                        rc = item.SetSource (0);

                if (LXx_FAIL (rc))
                        n_fail ++;

                return false;
        }
};

Execute – this changes the source item for all selected items, or at least tries to. There are many possible reasons for failure, including the fact that this is the item itself.

./Initialize_(index)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void init_InstanceSource ()
{
        CLxGenericPolymorph     *srv;

        srv = new CLxPolymorph<CInstSourceCommand>;
        srv->AddInterface (new CLxIfc_Command     <CInstSourceCommand>);
        srv->AddInterface (new CLxIfc_Attributes  <CInstSourceCommand>);
        srv->AddInterface (new CLxIfc_AttributesUI<CInstSourceCommand>);
        lx::AddServer ("instance.source", srv);
}

Setup our command as a server. It has a command interface, an attributes interface for arguments, and an attributesUI interface.

Helper Functions

1
 static CItemSelectionTracker   *sT = 0;

We only need one selection tracker for however many instances of commands, so we keep a count.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
       void
SelTrack_Acquire (void)
{
        if (sT) {
                sT->use_count++;
                return;
        }

        CLxUser_ListenerService  ls;

        sT = new CItemSelectionTracker;
        ls.AddListener (*sT);
}

        void
SelTrack_Release (void)
{
        sT->use_count--;
        if (sT->use_count)
                return;

        CLxUser_ListenerService  ls;

        ls.RemoveListener (*sT);
        delete sT;
        sT = 0;
}

These functions add and release selection trackers.

Implementations

1
2
3
4
5
6
7
 CInstSourceCommand::CInstSourceCommand ()
 {
        dyna_Add ("source", "&item");
        basic_SetFlags (0, LXfCMDARG_QUERY);

        SelTrack_Acquire ();
 }

This constructor adds an argument of the type &item and the name source as well as adding a selection tracker.

1
2
3
4
 CInstSourceCommand::~CInstSourceCommand ()
 {
        SelTrack_Release ();
 }

The destructor releases our current selection tracker.

1
2
3
4
 CInstSourceCommand::basic_CmdFlags ()
 {
        return LXfCMD_MODEL | LXfCMD_UNDO;
 }

The CmdFlags function indicates that our command makes changes to the model in Modo and support undo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
        bool
 CInstSourceCommand::basic_Notifier (
        int                      index,
        std::string             &name,
        std::string             &args)
 {
        if (index == 0) {
                name = "select.event";
                args = "item +v";               // VALUE on item selection

        } else if (index == 1) {
                name = "select.event";
                args = "item exist+d";          // DISABLE on item selection existence

        } else
                return false;

        return true;
 }

This function checks for if an item is currently selected or not; if one is, the command is disabled.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
         bool
 CInstSourceCommand::basic_Enable (
        CLxUser_Message         &msg)
 {
        CEnableItemVisitor       vis;

        vis.any = false;
        sT->Enumerate (vis);
        return vis.any;
 }

Accesses the methods created by the CEnableItemVisitor class to see if an item is currently selected or not.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
        LxResult
CInstSourceCommand::cmd_Query (
        unsigned int             index,
        ILxUnknownID             vaQuery)
{
        CQueryItemVisitor        vis;

        vis.va.set (vaQuery);
        sT->Enumerate (vis);
        return LXe_OK;
}

Accesses the methods created by the CQueryItemVisitor class to return a list of all selected item’s source items.

1
2
3
4
5
6
        CLxDynamicUIValue *
CInstSourceCommand::atrui_UIValue (
        unsigned int             index)
{
        return new CItemPopup;
}

Accesses the CItemPopup methods to customize the item popup.