Detect polygon selection change (revisited)
-
On 17/11/2017 at 01:09, xxxxxxxx wrote:
Andreas,
Well, I didn't look at it that way, but you are right about the hijacking.
The reason for doing that is that I don't want to re-implement all the available selection tools and all their options.
Additionally, with my own selection tools implemented, when the user double-click a selection tag, I wouldn't be able to detect the newly created selection. -
On 20/11/2017 at 08:33, xxxxxxxx wrote:
While I understand your reasoning, I unfortunately can't add much more. Currently Cinema 4D is not really prepared to re-use the functionality of for example the selection tools. In the end it will on you to decide between the drawbacks.
-
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 ... 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://developers.maxon.net/forum/topic/4175/3718_freeing-coupled-objects&PID=15178#15178
https://developers.maxon.net/forum/topic/5071/4996_catch-the-moment-when-the-user-converts-an-object&PID=20488#20488The 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)