On 21/11/2017 at 07:29, xxxxxxxx wrote:
I understand that hijacking the selection tools, or trying in one way or another to re-use their functionality is probably not meant to be done. But I also don't need to explain why one would try to avoid re-implementation all the possible selection tools and their options.
If only these tools would be provided as examples in the SDK (hint, hint :wink: ... but since internal implementations are not shared, that's probably not going to happen)
Anyway, I have been trying another approach, and at first it seemed to work.
I have searched the forums and this same technique -more or less- was described by Cactus Dan on multiple occasions:
https://plugincafe.maxon.net/topic/4175/3718_freeing-coupled-objects&PID=15178#15178
https://plugincafe.maxon.net/topic/5071/4996_catch-the-moment-when-the-user-converts-an-object&PID=20488#20488
The idea is to detect a change in selection. Undo that change, start a new undo, apply the selection change AND your own change.
This way both the selection and your required action are in a single user undo step.
Below is the complete code of a test plugin involving a MessageData and TagData.
Scenario:
Create a sphere, make editable, go into polygon mode, assign the MyTagData to the sphere, and select some polygons with the Live Selection tool -> all OK, press undo -> OK, redo -> OK
// Testing
#include "c4d.h"
// Dummy IDs - for demonstration purposes only
#define TAGDATA_PLUGIN_ID 1999998
#define MESSAGEDATA_PLUGIN_ID 1999999
#define TAG_BC_ID_SELECTION_COUNT 0
#define TAG_BC_ID_SELECTION_DATA 1
//
// TagData
//
class MyTagData : public TagData
{
INSTANCEOF(MyTagData, TagData)
public:
MyTagData() : TagData(), mIgnoreUndoRedo(FALSE) {}
static NodeData* Alloc(void) { return NewObjClear(MyTagData); }
virtual Bool Message(GeListNode* node, Int32 type, void* data)
{
if (type == MSG_DOCUMENTINFO)
{
DocumentInfoData* did = static_cast<DocumentInfoData*>(data);
if (did && ((did->type == MSG_DOCUMENTINFO_TYPE_UNDO) || (did->type == MSG_DOCUMENTINFO_TYPE_REDO))
&& !mIgnoreUndoRedo)
{
// update the selection in the tag container, in order for the MessageData
// to not see a change in selection and avoid calling CheckSelectionChange(),
maxon::BaseArray<Int32> added;
maxon::BaseArray<Int32> removed;
HasSelectionChanged(added, removed, TRUE);
}
}
return SUPER::Message(node, type, data);
}
// Custom methods
void CheckSelectionChange()
{
maxon::BaseArray<Int32> added;
maxon::BaseArray<Int32> removed;
if (HasSelectionChanged(added, removed, TRUE))
{
BaseTag* tag = (BaseTag* )Get();
if (!tag)
return;
BaseDocument* doc = tag->GetDocument();
if (!doc)
return;
BaseObject* object = tag->GetObject();
if (!object)
return;
GePrint("Selection change detected - Undo selection, redo selection + action");
// selection change detected, undo the selection change
mIgnoreUndoRedo = TRUE;
doc->DoUndo();
// we will now create our own undo stack,
// make the selection change and our own change
Bool ret = doc->StartUndo();
ret = doc->AddUndo(UNDOTYPE_CHANGE, object); // <- crashes when loop selection
// step 1. redo the selection change for the new undo stack
BaseSelect* polyS = ToPoly(object)->GetPolygonS();
for (const auto& item : added)
polyS->Select(item);
for (const auto& item : removed)
polyS->Deselect(item);
// step 2. perform the actual action
// dummy polygon vertex update
{
PolygonObject* polyObj = ToPoly(object);
const CPolygon* polyR = polyObj->GetPolygonR();
Vector* pointW = polyObj->GetPointW();
Int32 polyCount = polyObj->GetPolygonCount();
for (Int32 i = 0; i < polyCount; i++)
{
if (!polyS->IsSelected(i))
continue;
Vector center;
if (polyR[i].IsTriangle())
center = (pointW[polyR[i].a] + pointW[polyR[i].b] + pointW[polyR[i].c]) / 3;
else
center = (pointW[polyR[i].a] + pointW[polyR[i].b] + pointW[polyR[i].c] + pointW[polyR[i].d]) * 0.25;
pointW[polyR[i].a] = (pointW[polyR[i].a] + center) * 0.5;
pointW[polyR[i].b] = (pointW[polyR[i].b] + center) * 0.5;
pointW[polyR[i].c] = (pointW[polyR[i].c] + center) * 0.5;
pointW[polyR[i].d] = (pointW[polyR[i].d] + center) * 0.5;
}
}
ret = doc->EndUndo();
mIgnoreUndoRedo = FALSE;
GePrint("Selection change - Action completed");
}
}
protected:
// check if selection has changed, pass back the added and removed items,
// and optionally update the selection into the tag container
Bool HasSelectionChanged(maxon::BaseArray<Int32>& added, maxon::BaseArray<Int32>& removed, Bool update /*= FALSE*/)
{
AutoAlloc<BaseSelect> storedSelection;
if (!storedSelection)
return FALSE;
RetrieveSelection(storedSelection);
// compare the stored selection from the tag container with the current selection
BaseTag* tag = (BaseTag* )Get();
if (!tag)
return FALSE;
BaseObject* object = tag->GetObject();
if (!object)
return FALSE;
BaseSelect* polyS = ToPoly(object)->GetPolygonS();
// obtain the added and removed ones
Int32 count = ToPoly(object)->GetPolygonCount();
for (Int32 idx = 0; idx < count; idx++)
{
Bool newSel = polyS->IsSelected(idx);
Bool oldSel = storedSelection->IsSelected(idx);
if (newSel != oldSel)
{
if (oldSel)
removed.Append(idx);
if (newSel)
added.Append(idx);
}
}
if ((added.GetCount() == 0) && (removed.GetCount() == 0))
return FALSE;
// store the new selection when requested
if (update)
StoreSelection(polyS);
return TRUE;
}
// store selection into tag basecontainer
void StoreSelection(BaseSelect* selection)
{
if (!selection)
return;
BaseTag* tag = (BaseTag* )Get();
if (!tag)
return;
BaseContainer* data = tag->GetDataInstance();
if (!data)
return;
// populate the container
Int32 seg = 0, a, b, i, selCount = 0;
while (selection->GetRange(seg++, LIMIT<Int32>::MAX, &a, &b))
for (i = a; i <= b; ++i)
data->SetInt32(TAG_BC_ID_SELECTION_DATA + selCount++, i);
data->SetInt32(TAG_BC_ID_SELECTION_COUNT, selCount);
}
// retrieve selection from tag basecontainer
void RetrieveSelection(BaseSelect* selection)
{
if (!selection)
return;
selection->DeselectAll();
BaseTag* tag = (BaseTag* )Get();
if (!tag)
return;
const BaseContainer* data = tag->GetDataInstance();
if (!data)
return;
Int32 selCount = data->GetInt32(TAG_BC_ID_SELECTION_COUNT);
for (Int32 i = 0; i < selCount; i++)
selection->Select(data->GetInt32(TAG_BC_ID_SELECTION_DATA + i));
}
private:
Bool mIgnoreUndoRedo;
};
Bool RegisterMyTagData()
{
return RegisterTagPlugin(TAGDATA_PLUGIN_ID, "tagstring", TAG_VISIBLE, MyTagData::Alloc, "Tmytag", AutoBitmap("icon.png"), 0);
}
//
// MessageData
//
class MyMessageData : public MessageData
{
public:
virtual Bool CoreMessage(Int32 id, const BaseContainer &bc)
{
// we are only interested in EVMSG_CHANGE
if (id != EVMSG_CHANGE)
return TRUE;
BaseDocument* doc = GetActiveDocument();
if (!doc)
return TRUE;
BaseObject* object = doc->GetActiveObject();
if (!object || !object->IsInstanceOf(Opolygon))
return TRUE;
BaseTag* tag = object->GetTag(TAGDATA_PLUGIN_ID);
if (!tag)
return TRUE;
MyTagData* mytag = tag->GetNodeData<MyTagData>();
if (!mytag)
return TRUE;
mytag->CheckSelectionChange();
return TRUE;
}
};
Bool RegisterMyMessageData(void)
{
return RegisterMessagePlugin(MESSAGEDATA_PLUGIN_ID, String(), 0, NewObj(MyMessageData));
}
Bool PluginStart(void)
{
RegisterMyTagData();
RegisterMyMessageData();
return TRUE;
}
void PluginEnd(void)
{
}
Bool PluginMessage(Int32 id, void * data)
{
switch (id) {
case C4DPL_INIT_SYS:
if (!resource.Init())
return FALSE;
return TRUE;
case C4DMSG_PRIORITY:
return TRUE;
case C4DPL_BUILDMENU:
break;
case C4DPL_ENDACTIVITY:
return TRUE;
}
return FALSE;
}
This seemed to work ... at first.
Well, it does with live selection. But when a loop selection or ring selection is performed a crash happens when performing an AddUndo (after the DoUndo) in the CheckSelectionChange method of the tag.
So, why does this seem to work for some selection tools, and not for other ones?
Just trying to figure out, what I am doing wrong (except of abusing the whole SDK, that is)