SceneHook AddUndo [SOLVED]



  • On 27/08/2014 at 12:58, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R15 
    Platform:   Windows  ;   Mac OSX  ; 
    Language(s) :     C++  ;

    ---------
    Hello forum,

    I'm creating a plugin that keeps track of point selection order and re-draws the selected points with different colors according to each points selection order.  All of this is working fine.  I would like to add undo functionality to the selection process.

    After reading many posts and asking for advice(thank you), I have went down the road of implementing a SceneHook to store the selection data so modifications to that data can be put on the Undo Stack.  I have implemented FindPlugin() and a static_cast(UndoHookClass) to get a pointer to it. I have also implemented a pointer of type UndoHookClass as a member of MyToolClass.  I can access all of the members of UndoHookClass through this pointer, but when I try to AddUndo() with the pointer, I get crashes.  Below is a quick explanation of what I have tried in code:

    // partial code for simplicity and quicker understanding
    class UndoHook : public SceneHookData
    {
    public:
        virtual Bool CopyTo( NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn )
        {
            GePrint( String::IntToString( _sel_pts.GetCount() ) );
            for ( Int32 i = 0; i < _sel_pts.GetCount(); i++ )
            {
                static_cast<BPLup2UndoHook*>( dest )->_sel_pts.Append( _sel_pts[i] );
            }
            return NodeData::CopyTo( dest, snode, dnode, flags, trn );
        }
      
        void Append( Int32 v )
        {
            _sel_pts.Append( v );
        }
      
        void Erase( Int32 idx )
        {
            _sel_pts.Erase( idx );
        }
      
        // used for a test in MyTool
        // this does not work
        // void _AddUndo( BaseDocument* doc )
        // {
        //     doc->AddUndo( UNDOTYPE_CHANGE_SMALL, this );
        // }
      
        virtual Bool MouseInput( BaseSceneHook* node, BaseDocument* doc, BaseDraw* bd, EditorWindow* win, const BaseContainer& msg )
        {
            /*
            // testing
            // do not register MyTool in main.cpp and uncomment this out.
            // works like a charm.
            switch (msg.GetInt32(BFM_INPUT_QUALIFIER))
            {
                case QSHIFT:
                    doc->StartUndo();
                    doc->AddUndo(UNDOTYPE_CHANGE_SMALL, node);
                    Append(_sel_pts.GetCount());
                    doc->EndUndo();
                    return SceneHookData::MouseInput(node, doc, bd, win, msg);
                    break;
      
                case QCTRL:
                    if (_sel_pts.GetCount() > 0)
                    {
                        doc->StartUndo();
                        doc->AddUndo(UNDOTYPE_CHANGE_SMALL, node);
                        Erase(_sel_pts.GetCount() - 1);
                        doc->EndUndo();
                    }
                    else
                    {
                        GeOutString("array is at begining", GEMB_OK);
                    }
                    return SceneHookData::MouseInput(node, doc, bd, win, msg);
                    break;
      
                default:
                    PrintArray();
                    return SceneHookData::MouseInput(node, doc, bd, win, msg);
                    break;
            }
             */
      
            return SceneHookData::MouseInput( node, doc, bd, win, msg );
        }
        // tried making BlockArray public and private
        maxon::BlockArray<Int32> _sel_pts;
    };
      
    class MyTool : public DescriptionToolData
    {
    public:
        Bool InitUndoHook( BaseDocument* doc )
        {
            BasePlugin* base_plugin = FindPlugin( PID_UNDOHOOK, PLUGINTYPE_SCENEHOOK );
            _undo_hook = static_cast<UndoHook*>( base_plugin->GetPluginStructure() );
            return true;
        }
      
        void ModifyUndoHook( Int32 p, BaseDocument* doc )
        {
            // assume we have called InitUndoHook and all other members of UndoHook work fine when called inside MyTool class.
            doc->AddUndo( UNDOTYPE_CHANGE_SMALL, _undo_hook ); // crash! Xcode:  Thread 1: EXC_BAD_ACCESS(code=1, address-0x0)
      
            // tried this:
            doc->AddUndo( UNDOTYPE_CHANGE_SMALL, ( BaseSceneHook* )_undo_hook ); // crash! Xcode:  Thread 1: EXC_BAD_ACCESS(code=1, address-0x0)
      
            // tried this:
            doc->AddUndo( UNDOTYPE_CHANGE_SMALL, static_cast<BaseSceneHook*>( _undo_hook ) ); // This and other casts will not compile.
      
            // tried this:
            // uncomment out _AddUndo in UndoHook
            _undo_hook->_AddUndo( doc ); // crash! Xcode:  Thread 1: EXC_BAD_ACCESS(code=1, address-0x0)
      
            // tried this:
            BaseSceneHook* base_scene_hook = doc->FindSceneHook( PID_UNDOHOOK );
            doc->AddUndo( UNDOTYPE_CHANGE_SMALL, base_scene_hook ); // this does NOT crash.
            // UndoHook::CopyTo() is called right after this, but when checking the array count in UndoHook::CopyTo() with GePrint(),
            // a count of zero is always returned even when _undo_hook->_sel_pts->GetCount() is not zero.
      
            _undo_hook->Append( p ); // this and other modifications to the BlockArray through member functions work fine.
        }
      
    private:
        // tried making _undo_hook private and public
        UndoHook* _undo_hook;
    };
      
    
    

    Any suggestions on how to add an undo with a SceneHookData class would be greatly appreciated.

    Thank you,

    Joe Buck



  • On 27/08/2014 at 17:34, xxxxxxxx wrote:

    Just as it is not possible to add a BaseDocument as an undo pointed object, you probably cannot do the same with a SceneHook either.  Undos are for actions done and objects within a document.  The document itself and plugins that work outside of the document will not work this way.



  • On 27/08/2014 at 19:25, xxxxxxxx wrote:

    Thanks for looking at this for me Robert.  I read your posts about BaseDocument undos and thought it was sorted.  That is why I went down the road of SceneHooks for DescriptionToolData undos.

    SceneHookData is a descendant of NodeData.  In the post I provided an example in UndoHook::MouseInput(). There, AddUndo() is called with a second argument of BaseSceneHook*.  UndoHook also has a data member(maxon::BlockArray) that is not stored in the container.  UndoHook::CopyTo() is called automatically after AddUndo() is called and makes a deep copy, preparing it for the undo stack.  AddUndo() worked as expected when called inside the member function UndoHook::MouseInput().

    AddUndo() will crash C4D when it is called with a second argument of UndoHook* within the member function MyTool::ModifyUndoHook().

    In the code you can see that I have also tried doc->FindSceneHook(PID) which returns BaseSceneHook*. When I tried to use this pointer in AddUndo(), C4D did not crash but UndoHook::CopyTo() did not find the data to the data member _sel_pts.  Perhaps this data is truncated in AddUndo() or there is more than one SceneHook with this plugin id?

    I did think about using GePluginMessage(), but I would still have cast inside PluginMessage() like I have it in MyTool::InitUndoHook.  I'm assuming(my bad) that I would get the same results.

    I'm looking for a way to cast UndoHook* into something that can be used in AddUndo() inside a member function of MyTool or perhaps another way for the classes to communicate.

    Any thoughts or advice would be greatly appreciated.

    Thanks for your help,

    Joe Buck



  • On 20/09/2014 at 12:22, xxxxxxxx wrote:

    Hello Forum,

    I have solved this problem using a global variable and only implementing undos inside the SceneHook's virtual functions.

    Here is some simplified code outlining the use of global variables that you can compile and play with not needing the SDK:

    // shared_data.h
      
    #ifndef SHARED_DATA_H_
    #define SHARED_DATA_H_
      
    struct SharedData
    {
        int tool_setting;
        int hook_results;
    };
      
    // declare global variable
    // this can only contain POD(plain old data)
    // http://stackoverflow.com/questions/146452/what-are-pod-types-in-c
    // tip:  a pointer is considered POD
      
    extern SharedData shared_data;
      
    // this tells the complier that a variable of type SharedData named 
    // shared_data will be defined somewhere in the project
    // this variable cannot be accessed until it is defined
    // it is defined in shared_data.cpp
      
    #endif // SHARED_DATA_H_
    
    //  shared_data.cpp
      
    #include "shared_data.h"
      
    // define global variable
    // definition can be done in any .cpp file in the project
      
    SharedData shared_data;
      
    // now any file in this project can
    // access and modify this variable
    
    //  tool.h
      
    #ifndef TOOL_H_
    #define TOOL_H_
      
    class Tool
    {
    public:
        void UserChangedToolSetting(int v);
        void UserExecutedTool();
    };
    #endif // TOOL_H_
    
    //  tool.cpp
      
    #include <iostream>
    #include "shared_data.h"
    #include "tool.h"
      
    void Tool::UserChangedToolSetting(int v)
    {
        std::cout << "user changed tool setting = " << v << std::endl;
        // modify global variable
        shared_data.tool_setting = v;
    }
      
    void Tool::UserExecutedTool()
    {
        // access global variable
        std::cout << "user executed tool = " << shared_data.hook_results << std::endl;
    }
    
    //  hook.h
      
    #ifndef HOOK_H_
    #define HOOK_H_
      
    class Hook
    {
    public:
        void UserHookInput(int v);
    };
      
    #endif // HOOK_H_
    
    //  hook.cpp
      
    #include <iostream>
    #include "shared_data.h"
    #include "hook.h"
      
    void Hook::UserHookInput(int v)
    {
        std::cout << "user hook input = "  << v << std::endl;
        // access and modify global variable
        shared_data.hook_results = v + shared_data.tool_setting;
    }
    
    //  main.cpp
      
    #include "shared_data.h"
    #include "tool.h"
    #include "hook.h"
      
    int main() {
        Hook* hook = new Hook();
        Tool* tool = new Tool();
        
        tool->UserChangedToolSetting(5);
        hook->UserHookInput(5);
        tool->UserExecutedTool();
        
        delete hook;
        delete tool;
        return 0;
    }
    
    output:
    user changed tool setting = 5
    user hook input = 5
    user executed tool = 10
    Program ended with exit code: 0
    

    Please note that global variables MUST be POD(plain old data) or you can get exceptions.

    Joe Buck



  • On 20/09/2014 at 14:10, xxxxxxxx wrote:

    @joebuck why they should be POD? and why exceptions are bad with Cinema 4D, forgive me as my knowledge in this part is very limited



  • On 20/09/2014 at 14:25, xxxxxxxx wrote:

    Cinema 4D does not use exception handling so any thrown exceptions that are not caught could potentially 'pop the function stack' all the way back to the plugin's main thus crashing the plugin and most likely C4D.  Exceptions have this power to exit methods looking for a handler.  They can be used, but you must catch *every* thrown exception at the point of contact.



  • On 20/09/2014 at 14:36, xxxxxxxx wrote:

    @Robert thanks for clarification  , but one last question, why non POD global variables give exceptions?



  • On 20/09/2014 at 14:43, xxxxxxxx wrote:

    @Mohamed
    The answer to your question is way beyond the scope of my C++ knowledge.  Here is a link to another thread discussing this situation.

    https://plugincafe.maxon.net/topic/7929/10297_c4d-hangs-during-exit-with-our-plugin-on-mac&KW=global+crash&PID=39883#39883

    And in the R15 SDK documentation.



  • On 20/09/2014 at 15:47, xxxxxxxx wrote:

    @joebuck thanks a lot 🙂, this explains a lot


Log in to reply