Last active
May 19, 2022 19:42
-
-
Save mattcox/4742206 to your computer and use it in GitHub Desktop.
Match Transform Command for Luxology Modo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
matchXfrm.cpp | |
Sample plugin to demonstrate a simple command that matches | |
position, rotation and scale between multiple selected items. | |
This command operates slightly differently to the built in | |
match.position, rotation, scale commands. The built in commands | |
will average out the transform if multiple items are selected, | |
whereas we simply match all selected items to the last selected | |
item. However, it could be modified to make it work like the | |
internal commands. | |
*/ | |
/* | |
Include the required SDK header files. | |
*/ | |
#include "lxidef.h" | |
#include "lx_plugin.hpp" | |
#include "lxu_command.hpp" | |
#include "lx_select.hpp" | |
#include "lx_seltypes.hpp" | |
#include "lx_action.hpp" | |
#include "lx_locator.hpp" | |
#include "lx_stdDialog.hpp" | |
/* | |
Define the server names. | |
*/ | |
#define MATCHXFRM_CMD "item.matchXfrm" | |
/* | |
Define the functions and class for the item.matchXfrm command. | |
*/ | |
class matchXfrm_cmd : public CLxBasicCommand | |
{ | |
public: | |
CLxUser_CommandService cmd_svc; | |
CLxUser_SelectionService sel_svc; | |
CLxUser_StdDialogService dlg_svc; | |
CLxUser_MessageService msg_svc; | |
int basic_CmdFlags () LXx_OVERRIDE; | |
bool basic_Enable (CLxUser_Message &msg) LXx_OVERRIDE; | |
void cmd_Execute (unsigned int flags) LXx_OVERRIDE; | |
LxResult cmd_Desc (const char **desc) LXx_OVERRIDE; | |
LxResult cmd_ButtonName (const char **name) LXx_OVERRIDE; | |
LxResult cmd_UserName (const char **name) LXx_OVERRIDE; | |
}; | |
/* | |
Implementation of functions for the item.matchXfrm command. | |
*/ | |
int matchXfrm_cmd::basic_CmdFlags() | |
{ | |
/* | |
We're actually changing channels and item transforms here, | |
so this needs to be an undoable command, which basically | |
means that the user can undo and redo its actions. | |
*/ | |
return LXfCMD_UNDO; | |
} | |
bool matchXfrm_cmd::basic_Enable(CLxUser_Message &msg) | |
{ | |
/* | |
As the name suggests, this is where we tell modo if our command | |
is enabled or not, so it can be disabled in the interface. | |
We're going to simply check that at least two items are selected. | |
*/ | |
if(sel_svc.Count(LXiSEL_ITEM) >= 2) | |
{ | |
return true; | |
} | |
return false; | |
} | |
void matchXfrm_cmd::cmd_Execute(unsigned int flags) | |
{ | |
/* | |
The function is called to execute the command. We're going to | |
read the position, rotation and scale of the last selected item | |
and use it to write the position, rotation and scale of the | |
other selected items. | |
*/ | |
CLxUser_Item item_loc; | |
CLxUser_Locator locator_loc; | |
CLxUser_ItemPacketTranslation item_pkt; | |
CLxUser_ChannelRead chan_read; | |
CLxUser_ChannelWrite chan_write; | |
CLxUser_Message &msg = basic_Message(); | |
CLxUser_Scene scene; | |
CLxUser_Matrix rotation_matrix, position_matrix, scale_matrix; | |
LXtVector position_vector; | |
LXtMatrix rotation_matrix3; | |
LXtMatrix4 scale_matrix4; | |
unsigned sel_count = 0; | |
double current_time = 0.0; | |
item_pkt.autoInit(); | |
/* | |
Get the current selected item count. | |
*/ | |
sel_count = sel_svc.Count(LXiSEL_ITEM); | |
/* | |
Do another check to ensure that the user has two items selected. | |
However, if they haven't, they shouldn't have made it this far. | |
*/ | |
if(sel_count < 2) | |
{ | |
msg.SetCode(LXe_FAILED); | |
return; | |
} | |
/* | |
Get the last selected item. | |
*/ | |
void *pkt_0 = sel_svc.ByIndex(LXiSEL_ITEM, sel_count-1); | |
if(!item_pkt.test()) | |
{ | |
/* | |
Shouldn't really fail blindly, but tell the user why. | |
*/ | |
msg.SetCode(LXe_FAILED); | |
return; | |
} | |
/* | |
Localize the item in the packet. | |
*/ | |
item_pkt.Item(pkt_0, item_loc); | |
if(!item_loc.test()) | |
{ | |
/* | |
Again, it really should tell the user why it has failed. | |
*/ | |
msg.SetCode(LXe_FAILED); | |
return; | |
} | |
/* | |
Initialize the channel read interface, so we can read channels at | |
the current time. | |
*/ | |
current_time = sel_svc.GetTime(); | |
item_loc.GetContext(scene); | |
scene.GetChannels(chan_read, current_time); | |
/* | |
We want to get the position, rotation and scale of the first item. | |
*/ | |
chan_read.Object(item_loc, LXsICHAN_XFRMCORE_WPOSMATRIX, position_matrix); | |
chan_read.Object(item_loc, LXsICHAN_XFRMCORE_WROTMATRIX, rotation_matrix); | |
chan_read.Object(item_loc, LXsICHAN_XFRMCORE_WSCLMATRIX, scale_matrix); | |
/* | |
Check that our matrices are valid. | |
*/ | |
if(!position_matrix.test()||!rotation_matrix.test()||!scale_matrix.test()) | |
{ | |
/* | |
For some reason, we failed to read the position, rotation and scale. | |
Send a failed code to the user. | |
*/ | |
msg.SetCode(LXe_FAILED); | |
return; | |
} | |
/* | |
Extract the position vector and the rotation and scale matrices. | |
*/ | |
position_matrix.GetOffset(position_vector); | |
rotation_matrix.Get3(rotation_matrix3); | |
scale_matrix.Get4(scale_matrix4); | |
/* | |
Initialize the channel write interface, so we can write to | |
the current time and the edit action layer. | |
*/ | |
scene.SetChannels(chan_write, LXs_ACTIONLAYER_EDIT, current_time); | |
/* | |
At this point, we should have the transforms from the last selected | |
item. We want to loop through the other selected items in the scene | |
and set their transforms to match. | |
*/ | |
for(unsigned n = 0; n < sel_count-1; n++) | |
{ | |
void *pkt_1 = sel_svc.ByIndex(LXiSEL_ITEM, n); | |
/* | |
Localize the item in the packet. | |
*/ | |
item_pkt.Item(pkt_1, locator_loc); | |
if(!locator_loc.test()) | |
{ | |
/* | |
The locator wasn't localized, skip it. Although, this should | |
be handled a little more elegantly. | |
*/ | |
continue; | |
} | |
/* | |
Set the transform of the item to match the transform of the first. | |
For now, we'll assume compensation off. Not actually sure how to test | |
compensation state. | |
*/ | |
if(locator_loc.SetPosition (chan_read, chan_write, position_vector, LXiLOCATOR_WORLD, 0) != LXe_OK || | |
locator_loc.SetRotation (chan_read, chan_write, rotation_matrix3, LXiLOCATOR_WORLD, 0) != LXe_OK || | |
locator_loc.SetScale (chan_read, chan_write, scale_matrix4, LXiLOCATOR_WORLD, 0) != LXe_OK) | |
{ | |
/* | |
For whatever reason, we weren't able to set the transforms. | |
I'm unsure of how best to handle this, for now, we'll return | |
an error and stop the command. But that's not ideal. | |
*/ | |
msg.SetCode(LXe_FAILED); | |
return; | |
} | |
} | |
/* | |
At this point, we should have set the transform items on all the | |
selected items to match the transforms of the last selected item. | |
So return control to the user. | |
*/ | |
} | |
/* | |
We don't actually need to implement the following functions. However | |
they provide some useful help and tips for the user in the modo commands | |
list. So it won't hurt. | |
*/ | |
LxResult matchXfrm_cmd::cmd_Desc (const char **desc) | |
{ | |
/* | |
This sets the command description in the commands list. | |
*/ | |
desc[0] = "Matches the Position, Rotation and Scale of an item to another."; | |
return LXe_OK; | |
} | |
LxResult matchXfrm_cmd::cmd_ButtonName (const char **name) | |
{ | |
/* | |
This sets the default button name if the command is added | |
to a form. | |
*/ | |
name[0] = "Item Transform"; | |
return LXe_OK; | |
} | |
LxResult matchXfrm_cmd::cmd_UserName (const char **name) | |
{ | |
/* | |
This defines a user friendly name for the command. | |
*/ | |
name[0] = "Item Transform"; | |
return LXe_OK; | |
} | |
/* | |
Initialize the servers. | |
*/ | |
void initialize() | |
{ | |
CLxGenericPolymorph *srv; | |
srv = new CLxPolymorph <matchXfrm_cmd>; | |
srv->AddInterface (new CLxIfc_Command <matchXfrm_cmd>); | |
srv->AddInterface (new CLxIfc_Attributes <matchXfrm_cmd>); | |
srv->AddInterface (new CLxIfc_AttributesUI <matchXfrm_cmd>); | |
thisModule.AddServer (MATCHXFRM_CMD, srv); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment