Plugin Cafe Homepage
Forum Home Forum Home > Plugin Cafe > SDK Help
  New Posts New Posts
  FAQ FAQ  Forum Search   Register Register  Login Login

Detect polygon selection change (revisited)

 Post Reply Post Reply
Author
Message
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Topic: Detect polygon selection change (revisited)
    Posted: 2017 Nov 14 at 5:44am

User Information:

Cinema 4D Version:   R19 
Platform:   Windows  ;   
Language(s):     C++  ;   

---------

I mention the original topic for informational purpose only:
http://www.plugincafe.com/forum/forum_posts.asp?TID=13344&KW=&PID=52915#52915

In the past I used a MessageData plugin to listen to EVMSG_CHANGE to detect changes in polygon selection performed by the user.


class TestMessageData : public MessageData
{
public:

    virtual Bool CoreMessage(Int32 id, const BaseContainer &bc);

    UInt32    mLastDirtyChecksum;
};

Bool TestMessageData::CoreMessage(Int32 id, const BaseContainer &bc)
{
    // to detect if user has changed polygon selection we listen to EVMSG_CHANGE,
    // and skip processing any other message
    if (id != EVMSG_CHANGE)
        return true;

    BaseDocument* doc = GetActiveDocument();
    if (!doc)
        return true;

    BaseObject* object = doc->GetActiveObject();
    if (!object || !object->IsInstanceOf(Opolygon))
    {
        mLastDirtyChecksum = 0;
        return true;
    }

    // !!! simplified detection of change of polygon selection !!!

    UInt32 objectDirty = object->GetDirty(DIRTYFLAGS_SELECT);
    if (mLastDirtyChecksum != objectDirty)
    {
        mLastDirtyChecksum = objectDirty;

        // if 'undo' then bail out, else perform the action ...


When user has made selection changes and then presses the undo button, this triggers the detection twice with increasing dirty values. As such, there is no way to know if a change was the result of a direct user action, or as a result of an undo.

Since undos cannot be detected from a MessageData, I am thus looking for an alternative solution to detect selection changes, only resulting from user interactions.

Using a SceneHookData I could detect the undo, but I see no way to combine the two (detect selection changes + detect undo) to omit detecting the selection changes as a result of undos.


Thanks,
Daniel


Edited by C4DS - 2017 Nov 14 at 5:45am
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1822
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 15 at 9:13am
Hi Daniel,

no really good news here. We are not aware of a good way to achieve this. Sorry.

But maybe we can try it the other way round. Why do you want to avoid acting on selections changed due to undo? In the end the selection changed, right? Maybe we can come up with another idea, if we understand the use-case.

Cheers,
Andreas
SDK Support Engineer
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 15 at 9:35am
Assume the following:
- when a user selects a polygon, calculation is performed on its vertices, affecting their positions.
- when a user deselects a polygon, another calculation is performed, taking the neighbor polygons into account, again affecting the positions of vertices of the polygon being deselected.
- both calculation do not cancel each other out.

As such, if a user selects and deselects a polygon the end result is not returning the vertices in their original position.
Since currently an undo is detected as if user deselects the selected polygon, there is no way for the user to revert a change.

Back to Top
gr4ph0s View Drop Down
Member
Member


Joined: 2015 Jul 07
Location: France
Online Status: Offline
Posts: 437
Post Options Post Options   Quote gr4ph0s Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 15 at 9:43am
Maybe you could get/store a clone of obj before the position change.
Then if you detect undo, set clonedObj->CopyTo(obj)


Technical lover.
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 16 at 6:51am
I don't think this cloning is a workable solution.

First of all, it's not a single undo step we're talking about. And between polygon selection actions the user can also do other things. Which means I should take a new clone at every polygon selection action, in case the user has changed other things. So, I would basically implement my own undo system in parallel to that of MAXON's. In one way or another I fear synchronization between the two undo systems will go south sooner than later.

Secondly, it's especially the "detect undo" which is the difficult part. If I can detect that the undo is related to change of polygon selection, and I would copy the cloned object back, then it's also as "simple" as not responding to the polygon selection change in the first place.

Detecting undo is feasible. Detecting that a polygon selection change occurred is feasible. Detecting both combined isn't that straightforward.
There isn't an <undo begins here> and <undo ends here>. That would allow me to detect if a selection change happens between (and thus ignore it).
Not that it matters but I am not sure what the MSG_DOCUMENTINFO_TYPE_UNDO and MSG_DOCUMENTINFO_TYPE_REDO actually indicate: is it <undo begins>, or <undo ends>? Or even <undo is ongoing>?


Edited by C4DS - 2017 Nov 16 at 6:53am
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1822
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 16 at 8:59am
Hi Daniel,

we are not sure about the entire approach. You are basically hi-jacking the selection tools, and thus end up the above issues.
Wouldn't it be a better approach to implement your own tool? This could then of course also change the selection.

Cheers,
Andreas
SDK Support Engineer
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 17 at 1:09am
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.
Back to Top
Andreas Block View Drop Down
Forum Moderator
Forum Moderator
Avatar

Joined: 2014 Oct 01
Location: Hannover
Online Status: Offline
Posts: 1822
Post Options Post Options   Quote Andreas Block Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 20 at 8:33am
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.

Cheers,
Andreas
SDK Support Engineer
Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2017 Nov 21 at 7:29am
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:
http://www.plugincafe.com/forum/forum_posts.asp?TID=3718&PID=15178#15178
http://www.plugincafe.com/forum/forum_posts.asp?TID=4996&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)

Back to Top
C4DS View Drop Down
Member
Member


Joined: 2015 Dec 01
Online Status: Offline
Posts: 233
Post Options Post Options   Quote C4DS Quote  Post ReplyReply Direct Link To This Post Posted: 2018 Feb 09 at 12:29am
Bringing this last comment back on the radar.
(probably partially related with http://www.plugincafe.com/forum/forum_posts.asp?TID=13943 )
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down

Bulletin Board Software by Web Wiz Forums® version 9.61 [Free Express Edition]
Copyright ©2001-2009 Web Wiz

This page was generated in 0.109 seconds.