Command VertValue

From The Foundry MODO SDK wiki
Revision as of 21:12, 15 October 2013 by Jangell (Talk | contribs)

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

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.

Functionality

Code Walkthrough

VertValue.cpp

Class Declarations

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

 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

	  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 CLxImpl_AbstractVisitor, which has all the necessary methods.

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.


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 basic command we inherit from CLxBasicCommand.

Initialize

 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

        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.

 void cleanup ()
 {
        if (pd)
                delete pd;
 }

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

Implementations

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

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

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.

      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.

        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.

     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.

        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.

        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.

       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.

        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.

        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

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.

 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.

 class CEnableItemVisitor : public CItemVisitor
 {
    public:
        bool		 any;
 
        bool Item (CLxUser_Item &item)
        {
                any = true;
                return true;
        }
 };

Enable -- test if there's anything selected.

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

 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.

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

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

static CItemSelectionTracker	*sT = 0;

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


       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

 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.

 CInstSourceCommand::~CInstSourceCommand ()
 {
        SelTrack_Release ();
 }

The destructor releases our current selection tracker.

 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.

        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.

         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.

        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.

        CLxDynamicUIValue *
CInstSourceCommand::atrui_UIValue (
        unsigned int		 index)
{
        return new CItemPopup;
}

Accesses the CItemPopup methods to customize the item popup.