I would like to point to an older discussion, which didn't provide any answer then.
https://plugincafe.maxon.net/topic/10457/13898_detect-polygon-selection-change-revisited
I am now again encountering this problem ...
I have attached code for a dummy plugin which can be used to reproduce the problem (note that you will need an icon file for this plugin, not included)
The idea is to perform a polygon selection on an object, and call the plugin.
The plugin will perform some actions on the polygons. But one would want to have AND the polygon selection AND the action on the polygon into a single undo step.
This way, the user simply has to undo once. There's more behind the reason for having a single undo step, but let's leave this out of the discussion.
One way to look at this is to provide an own selection tool. Been there, done that.
The other solution (in order to re-use the native selection tools) is to detect the change, undo the change, create a new undo and in this one perform the selection change AND the processing of the polygons.
This is what the implementation below does.
So, with the plugin available, you an create a cube, make it editable.
With the live selection tool you select a polygon, then shift-select another polygon.
The two polygons will be part of 2 separate undo steps.
If you now activate the plugin it will:
- undo the last step (the last selected polygon will become unselected)
- create a new undo
- re-select the last selected polygon
- do our own thing
- end the new undo
This all works as expected with Live Selection tool.
But if the last undo step -before calling the plugin- was a result of the Loop Selection tool, or the Ring Selection tool, ... then we get an exception when a new doc->AddUndo is performed, right after the doc->DoUndo.
Is this a bug?
Any workaround to make it work for any selection tool?
// ========================
// Cinema 4D C++ plugin
//
// PluginName: Test
// Dummy "empty" plugin
// ========================
// Main.cpp
#include "c4d.h"
// Dummy IDs - for demonstration purposes only
#define MYCOMMAND_PLUGIN_ID 1000000
// ====================================
// CommandData
// ====================================
class MyCommand : public CommandData
{
INSTANCEOF(MyCommand, CommandData)
public:
//virtual Bool Execute(BaseDocument* doc); // R20
virtual Bool Execute(BaseDocument* doc, GeDialog* parentManager);
};
//Bool MyCommand::Execute(BaseDocument* doc) // R20
Bool MyCommand::Execute(BaseDocument* doc, GeDialog* parentManager)
{
if (!doc)
return false;
BaseObject* obj = doc->GetActiveObject();
if (!obj)
{
ApplicationOutput("No active object!");
return false;
}
ApplicationOutput("Current active object = @", maxon::String(obj->GetName()));
BaseSelect* polyS = ToPoly(obj)->GetPolygonS();
if (!polyS)
{
ApplicationOutput("No selected polygons!");
return false;
}
// undo the last step (assuming it is a polygon selection change)
// make a copy of the selection,
// since the original selection will be undone
AutoFree<BaseSelect> polyScopy((BaseSelect*)polyS->GetClone());
if (!doc->DoUndo())
{
ApplicationOutput("Failed to DoUndo()");
return false;
}
// we now want to apply the previous changes again, but include our own changes into the same undo
doc->StartUndo();
doc->AddUndo(UNDOTYPE::CHANGE_SELECTION, obj);
// set the edges that weren't set
// Note: obviously we also need to unset the ones that had been deselected,
// which we will not do in this demonstration plugin
{
Int32 seg = 0, a, b, i;
while (polyScopy->GetRange(seg++, LIMIT<Int32>::MAX, &a, &b))
{
for (i = a; i <= b; ++i)
{
if (!polyS->IsSelected(i))
{
polyS->Select(i);
}
}
}
}
// do our own undo steps
{
// ..
}
doc->EndUndo();
EventAdd();
ApplicationOutput("Undo completed");
return TRUE;
}
Bool RegisterMyCommand(void)
{
return RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Test"_s, 0, AutoBitmap("icon.png"_s), "Test"_s, NewObjClear(MyCommand));
}
// ====================================
// Plugin Main
// ====================================
Bool PluginStart(void)
{
ApplicationOutput("Test"_s);
RegisterMyCommand();
return TRUE;
}
void PluginEnd(void)
{
}
Bool PluginMessage(Int32 id, void * data)
{
switch (id) {
case C4DPL_INIT_SYS:
if (!g_resource.Init())
return FALSE;
return TRUE;
case C4DMSG_PRIORITY:
return TRUE;
case C4DPL_BUILDMENU:
break;
case C4DPL_ENDACTIVITY:
return TRUE;
}
return FALSE;
}
EDIT:
I have adjusted the included code to be R21 compliant, as the originally provided implementation came from R20.