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.