Example cmd sy partifymesh¶
Introduction¶
This article details in laymans terms adding a ‘new’ command to modo. It has been written by an SDK end user, so may or may not be representative of ‘good’ or even accurate information.
Planning¶
It’s good practice to note down a few details describing the scope of what your command does, when it does it and how it may be activated so as to give yourself some guidelines to work too. So…
- ‘’What should this command do?’’
This command should ‘apply a unique part tag to each polygon in the currently selected mesh’.
- ‘’What should this command be called?’’
Based on what the command is supposed to do and whom has made it a good name for this command would be ‘’’’sy.partifymesh’’’’.
- ‘’When should this command be allowed to run?’’
This command should only be ‘enabled’ when the user has a ‘mesh’ item selected AND that mesh item has some polygons.
- ‘’Is the command going to possibly run from a button?’’
For this particular example we’ll make the command suitable for running from a button. So, we’ll detail help information and images that will automatically apply when the command is mapped to a button.
Implementation¶
Code¶
A basic ‘code flow’ summary follows, it’s not in any particular order of operation :
The module’s initialize method is called which in turn calls the namespace’s initialize method.
The namespace initialize registers the ‘server’ with appropriate interfaces for this plugin, which is how modo knows that these servers exist.
When the command is activated within modo the constructor sets a CLxUser_Log. The fact that the log subsystem even exists is set up via server tags (LXtTagInfoDesc ). Note that this log is purely for debugging purposes, and should not be present in shipping code.
The runtime acquires the flags from this plugin via the basic_CmdFlags method, which we use to report that this command performs undoable actions, in this case by calling functions that modify the mesh.
The runtime calls the basic_Enable method to see if this command can be run or not, and which is used to mark the command as disabled or enabled in the user interface. If the command is disabled, cmd_Execute will not be called.
When added to as a button in Form View, the runtime calls into basic_AddNotifiers, which in turn essentially registers what events this plugin will receive notification for and how to invalidate the user interface when a relevant event occurs. In this case, we’re listening for changes to the item selection, and that we want it to notify the user interface to refresh our button’s enable state when that occurs.
Finally, the runtime will call the plugin’s cmd_Execute method if the command is enabled. Errors are reported through the command’s CLxUser_Message, which is obtained through a call to the basic_Message method on our object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* synide@rocketmail.com */
#include <lx_plugin.hpp>
namespace sy_partifymesh_cmd { extern void initialize();
extern void cleanup(); };
void initialize() {
sy_partifymesh_cmd ::initialize();
};
void cleanup() {
sy_partifymesh_cmd ::cleanup();
};
|
sy_partifymesh_cmd.cpp¶
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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | /* synide@rocketmail.com */
//=====================================
// Notes:
// April, 2012 - Revised for Liki example.
// May, 2012 - A More comprehensive implementation.
// Added preprocessor directives to disabled EventLog output when not in Debug.
// Added 'meshes.event' to facilitate disabling if the mesh is emptied of polygons.
// Added a 'completion' message.
#include <time.h>
#include <string>
#include <sstream>
#include <lxidef.h>
#include <lx_plugin.hpp>
#include <lx_layer.hpp>
#include <lx_mesh.hpp>
#include <lx_log.hpp>
#include <lxu_command.hpp>
#include <lxu_select.hpp>
namespace sy_partifymesh_cmd
{
#ifndef __NAMESPACE_STATICS
static char *SERVER_NAME = "sy.partifymesh";
static char *SERVER_USERNAME = "Sy PartifyMesh";
static char *LOG_NAME = "sy/cmds/partifymesh";
static CLxItemType iType_Mesh (LXsITYPE_MESH);
#endif __NAMESPACE_STATICS
std::string fnI2S(unsigned value, bool notnullterminated = true, unsigned padding = 0, bool asHexadecimal = false, bool withHexPrefix = false)
{
std::ostringstream os;
std::string result;
if (asHexadecimal) {os << std::hex;}
if (value < 10 && padding) {os << 0;}
if (value < 100 && padding > 1) {os << 0;}
if (value < 1000 && padding > 2) {os << 0;}
if (notnullterminated)
os << value;
else
os << value << std::ends;
if (asHexadecimal && withHexPrefix) {result = std::string("0x");}
result += os.str();
return result;
};
std::string fnF2Sfixed(float value, bool nullterminated = false, unsigned p = 1)
{
std::ostringstream os;
os.setf(std::ios::fixed);
os.precision(p);
if (nullterminated)
os << value << std::ends;
else
os << value;
return os.str();
};
/*
* 'sy.partifymesh' command is derived from 'CLxBasicCommand'.
*
*/
class CSyPartifyMesh : public CLxBasicCommand {
public:
static LXtTagInfoDesc descInfo[];
CLxUser_LayerService LayerService;
#ifdef _DEBUG
CLxUser_Log PartifyMeshLog;
CSyPartifyMesh() {
PartifyMeshLog.setByName(LOG_NAME);
};
#endif _DEBUG
void basic_Notifiers() override {
basic_AddNotify(LXsNOTIFIER_SELECT, "item +d"); /* Will be notified when Item selection changes */
basic_AddNotify(LXsNOTIFIER_MESHES, "+p +d"); /* Will be notified on polygon changes in the mesh */
};
bool basic_Enable(CLxUser_Message &msg) override {
/*
Utilize the new (601) CLxItemSelectionType to find the first mesh item and then check if it
has any polygons.
*/
bool result(false);
CLxItemSelectionType TypedItemSelector(LXsITYPE_MESH);
CLxUser_Item theItem;
const char *ItemName;
if (TypedItemSelector.GetFirst(theItem) && theItem.test())
{
if (theItem.Type() == iType_Mesh.Type())
{
CLxUser_Mesh theMesh;
CLxUser_ChannelRead aChanReader;
if (aChanReader.from(theItem))
{
if (aChanReader.Object(theItem, LXsICHAN_MESH_MESH, theMesh))
{
result = theMesh.NPolygons() > 0;
if (!result)
{
theItem.UniqueName(&ItemName);
if (ItemName) {
msg.SetCode(LXe_FAILED);
msg.SetMessage(SERVER_NAME, "ItemHasZeroPolygons", 0);
msg.SetArgumentString(1, ItemName);
}
}
}
else
{
/* Unlikely to ever get here */
msg.SetCode(LXe_CMD_DISABLED);
msg.SetMessage(SERVER_NAME, "NoValidMesh", 0);
}
}
aChanReader.clear();
theMesh.clear();
}
else
{
msg.SetCode(LXe_CMD_DISABLED);
msg.SetMessage(SERVER_NAME, "NoValidMesh", 0);
}
theItem.clear();
}
else
{
msg.SetCode(LXe_CMD_DISABLED);
msg.SetMessage(SERVER_NAME, "NoValidMesh", 0);
}
return result;
};
int basic_CmdFlags() override {
return LXfCMD_UNDO;
};
void cmd_Execute(unsigned int flags) override {
/*
We restrict the LayerScan's results to the 'Primary' selected mesh only so that we
guard against the user having multiple 'selected' meshs which could result in overload.
*/
CLxUser_LayerScan LayerScan;
CLxUser_Item theItem;
CLxUser_Mesh theMesh;
CLxUser_Polygon thePolygon;
CLxUser_StringTag theTag;
const char *ItemName;
LayerService.BeginScan(LXf_LAYERSCAN_PRIMARY | LXf_LAYERSCAN_WRITEMESH, LayerScan);
if (LayerScan.ItemByIndex(0, theItem))
theItem.UniqueName(&ItemName);
/* As there should only ever be 1 mesh found by the LayerScan it's index should be zero. */
if (LayerScan.EditMeshByIndex(0, theMesh))
{
int NoOfPolygons = theMesh.NPolygons();
// Set 'thePolygon' as the polygon accessor for the 'theMesh'.
thePolygon.fromMeshObj(theMesh);
// Set 'theTag' as the 'tags' accessor for the polygon.
theTag.set(thePolygon);
std::string tag;
clock_t timerStart = clock();
#ifdef _DEBUG
unsigned msg_stride = NoOfPolygons / 5;
unsigned msg_counter = 1;
#endif _DEBUG
for (int i1 = 0; i1 < NoOfPolygons; i1++)
{
/*Ask the polygon accessor to populate itself based on the poly index. */
thePolygon.SelectByIndex(i1);
/* Construct a unique 'part' tag name. */
tag = "poly_" + fnI2S(i1);
/* Ask the 'tags' accessor to set the 'PART' tag. */
theTag.Set(LXi_PTAG_PART, tag.c_str());
#ifdef _DEBUG
//logging at 20%, 40%, 60%, 80%
if (PartifyMeshLog && (i1 == (msg_stride * msg_counter)))
{
clock_t timerDiff = clock() - timerStart;
double duration = (double)timerDiff / (double)CLOCKS_PER_SEC;
switch(msg_counter)
{
case 1:
PartifyMeshLog.Message(LXe_INFO, "20\%%, %u polygons in %.6f seconds.", i1, duration);
break;
case 2:
PartifyMeshLog.Message(LXe_INFO, "40\%%, %u polygons in %.6f seconds.", i1, duration);
break;
case 3:
PartifyMeshLog.Message(LXe_INFO, "60\%%, %u polygons in %.6f seconds.", i1, duration);
break;
case 4:
PartifyMeshLog.Message(LXe_INFO, "80\%%, %u polygons in %.6f seconds.", i1, duration);
break;
}
msg_counter += 1;
}
#endif _DEBUG
}
/* Tell the LayerScan that we are only 'editing' polygon tags. */
LayerScan.SetMeshChange(0, LXf_MESHEDIT_POL_TAGS);
/* Apply the mesh changes. */
LayerScan.Apply();
clock_t timerDiff = clock() - timerStart;
double duration = (double)timerDiff / (double)CLOCKS_PER_SEC;
basic_Message().SetCode(LXe_INFO);
basic_Message().SetMessage(SERVER_NAME, "Complete", 0);
basic_Message().SetArgumentInt(1, NoOfPolygons);
basic_Message().SetArgumentString(2, fnF2Sfixed((float)duration,false,6).c_str());
#ifdef _DEBUG
PartifyMeshLog.Message(LXe_INFO, "100\%%, %u polygons in %.6f seconds.", NoOfPolygons, duration);
#endif _DEBUG
}
else
{
/* Unlikely to ever get here */
basic_Message().SetCode(LXe_CMD_DISABLED);
basic_Message().SetMessage(SERVER_NAME, "NoValidMesh", 0);
#ifdef _DEBUG
PartifyMeshLog.Message(LXe_NOTFOUND, "No valid mesh found!");
#endif _DEBUG
}
};
};
LXtTagInfoDesc CSyPartifyMesh::descInfo[] = {
{LXsSRV_LOGSUBSYSTEM, LOG_NAME},
{LXsSRV_USERNAME, SERVER_USERNAME},
{0}
};
void initialize() {
CLxGenericPolymorph *srv = new CLxPolymorph<CSyPartifyMesh>;
srv->AddInterface(new CLxIfc_Command <CSyPartifyMesh>);
srv->AddInterface(new CLxIfc_StaticDesc <CSyPartifyMesh>);
lx::AddServer(SERVER_NAME, srv);
};
void cleanup() {};
};//namespace sy_partifymesh_cmd
|
Config¶
The following xml file is what’s referred to as a Configs. This particular config has the following features :-
A new command category is created to apportion this particular command under ‘Sy/Cmds’ in the modo command tree listing.
We specify command help details regarding what the command does.
We define a message table for localized error messages.
We specify a help url for this command.
We detail some UI elements relating to icons. See Icon Resources for more details.
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 | <?xml version="1.0" encoding="UTF-8"?>
<configuration>
<atom type="Categories">
<hash type="Category" key="Commands">
<hash type="C" key="sy.partifymesh">Sy/Cmds</hash>
</hash>
</atom>
<atom type="CommandHelp">
<hash type="Command" key="sy.partifymesh@en_US">
<atom type="UserName">PartifyMesh</atom>
<atom type="Desc">Apply unique 'part' tag to each polygon in selected mesh.</atom>
<atom type="Tooltip">Apply unique 'part' tag to each polygon in selected mesh.</atom>
<atom type="ButtonName">PartifyMesh</atom>
<atom type="Example">sy.partifymesh</atom>
</hash>
</atom>
<atom type="Messages">
<hash type="Table" key="sy.partifymesh.en_US">
<hash type="T" key="ItemHasZeroPolygons">"%1" has zero polygons.</hash>
<hash type="T" key="NoValidMesh">No valid mesh selected.</hash>
<hash type="T" key="Complete">"%1" polygons completed in "%2" seconds.</hash>
</hash>
</atom>
<atom type="HelpURLs">
<hash type="HelpURL" key="command:sy.partifymesh">kit_SyCmds:help\index.html#partifymesh</hash>
</atom>
<atom type="UIElements">
<hash type="Image" key="sy_cmd_icons">sy_cmd_icons.png</hash>
<!-- First row of image -->
<hash type="Icon" key="sy.partifymesh_20"> <atom type="Source">sy_cmd_icons</atom> <atom type="Location">0 0 20 20</atom> </hash>
<!-- Second row of image -->
<hash type="Icon" key="sy.partifymesh_32"> <atom type="Source">sy_cmd_icons</atom> <atom type="Location">0 20 32 32</atom> </hash>
</atom>
</configuration>
</source>
|
Icon Resources¶
The following .png file is provided as an example of the icon images that commands mapped to UI buttons may implement, the display of which is an automated process. When the command returns ‘false’/LXe_CMD_DISABLED from basic_Enable, the system automatically generates a desaturated version of the icon image for the button. See the Icon Resources article for more details.