Skip to content

Instantly share code, notes, and snippets.

@mattcox
Last active May 19, 2022 19:42
Show Gist options
  • Save mattcox/4742206 to your computer and use it in GitHub Desktop.
Save mattcox/4742206 to your computer and use it in GitHub Desktop.
Match Transform Command for Luxology Modo.
/*
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