Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Reset Tool in Interaction Tag

    Cinema 4D SDK
    2024 python
    2
    4
    573
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • CJtheTigerC
      CJtheTiger
      last edited by CJtheTiger

      Hello coders,

      I was playing around with the Interaction Tag. I'll call the object holding the Interaction Tag the Controller and the proxy object the Proxy. Here's what I want to do:

      • When the user drags the Controller using the left mouse button the Proxy should be moved.
      • When the user drags the Controller using the right mouse button the Proxy should be scaled.

      So first I enable Use Right Mouse Button in the Output tab, then in the Scripting tab in the Python script in mouseDown() I check for the right mouse button and eventually set the relevant tool:

      def mouseDown():    
              bc = c4d.BaseContainer()    
              c4d.gui.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, bc)    
              right_mouse_down = bc[c4d.BFM_INPUT_VALUE] == True  
          
              if right_mouse_down:    
                  doc.SetAction(200000089) # Scale    
              else:    
                  doc.SetAction(200000088) # Move
      

      So far so good. But when right-click-dragging the Proxy will be scaled using the shared axis of the Proxy and the Controller:
      1c6c1554-adca-4d51-8634-a821e46c419d-Cinema_4D_pl2YX5S7O9.gif
      This already seemed weird but I tried to work around it because obviously I want to scale the object on its own axis. So I added this line to the script to enable the Per-Object Transform of the scale tool after activating it:

              tool_data = doc.GetActiveToolData()
              tool_data[c4d.MDATA_AXIS_LOCALMANIPULATION] = True
      

      eb64552e-5282-4ae8-ada0-4f12b896a3da-Cinema_4D_fFuoaUeo2J.gif

      Works fine. However being a good little developer I want to reset the tool to its original state after we're done. The C4D help states for the function mouseUp() of the Interaction Tag:

      mouseUp
      This is called when the user releases the mouse button and signals the end of the user interaction. You can reset tools or kill off any memory-hungry objects that have been allocated in mouseDown or mouseDrag at this point.

      But how do I reset the tool? I do not want to just set the flag to False since the user might've set it to True deliberately regardless of my Interaction Tag so it should regain this expected state.


      Demo

      Here's a simple demo scene (2024.2.0) on my personal OneDrive:
      https://1drv.ms/u/s!At78FKXjEGEomLQx70lV4DUgjN5dbw?e=iwNum5

      To see the behavior:

      1. Activate the Scale tool.
      2. Set the flag Per-Object-Transform to True.
      3. Activate another tool (for example move or rotate).
      4. Make sure no object is selected, then right-click-drag the sphere which will scale the cube.
      5. After releasing the right mouse button, activate the Scale tool and check the flag Per-Object-Transform. It is now unset.

      Intentional Shared-Axis-Behavior?

      I'm also not sure whether this shared-axis-behavior is intentional? Because if you're trying to manipulate a proxy using this tag you most likely want to do it while completely disregarding the axis of the object holding the tag. Correct me if I'm wrong.

      Cheers,
      Daniel

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @CJtheTiger
        last edited by ferdinand

        Hey @CJtheTiger,

        Thank you for reaching out to us. Despite you clear efforts to make this very readable, I struggle a bit with finding/understanding here the main question. As lined out in Support Procedures: Asking Questions, we strongly recommend placing your major question at the very beginning of a topic.

        When I would summarize your problem, the question seems to be "How can I restore the state of a (tool) data container in a scripting object script which has been modified by that script before?".

        The underlying problem is that Scripting Object scripts, e.g., your Interaction Tag are in tendency volatile. I.e., other than in a plugin, you cannot build upon the fact that a module object ("the script") has an (almost) infinite lifetime. And because of that you cannot store things indefinitely in a global module attribute ("global variable") as Cinema 4D might decide to re-init the Scripting Object module and with that flush your global variable. There are two primary patterns to deal with this:

        1. Storing things in a Python object with (near) indefinite life time, usually a module. This is somewhat an anti-/no-no-pattern, but at least I use it sometimes. We should be careful with not overwriting things in a module that is not our data. With this, a script can 'remember' things that happend in the past.
        import c4d
        import mxutils
        
        def foo() -> None:
            """
            """
            # Get our data we attached to a module object we know lives for a long time. We use here c4d,
            # but we could also use sys, os, math, etc.
            data: list[int] | None = getattr(c4d, "myStuff", None)
            if data is None:
                data = [1, 2, 3, 4, 5]
            
            # Assert that #data is what we think it is.
            mxutils.CheckIterable(data, int, list)
        
            # Do some computing.
            data = [n ** 2 for n in data]
        
            # Write the data back.
            setattr(c4d, "myStuff", data)
        
        1. The other pattern only works with Scripting Object scripts (and not with Script Manager scripts) as it requires a node. Here we simply store our data in data container of the node. The drawbacks are here that the data container of a node requires us to serialize things into a BaseContainer form, and that we need a plugin ID to securely store things.
        """
        """
        import c4d
        import random
        
        doc: c4d.documents.BaseDocument  # The document containing this tag.
        tag: c4d.BaseTag # The Interaction tag containing this code.
        
        
        local_manipulation_state_id = 999999
        
        ID_DATA_STORAGE: int = 1061998 # A plugin ID under which we can safely store things.
        
        def mouseDown():
            """
            """
            # We could store data in the interaction tag or the object holding the tag (in case we want
            # multiple tags on that object to access the data). We could also store things in the document
            # since it is also a node, but that is not so desirable for multiple reasons.
            data: c4d.BaseContainer = tag.GetDataInstance()
            print (f"The last object message is:  {data[ID_DATA_STORAGE]}")
        
            newMessage: str = "".join(random.choices(["a", "b", "c", "d", "e", "f", "g"], k=10))
            print (f"Setting new message: {newMessage}")
            data[ID_DATA_STORAGE] = newMessage
        
            # ----------------------------------------------------------------------------------------------
        
            def PrintContainer(container: c4d.BaseContainer) -> None:
                """
                """
                for key, val in container:
                    print (key, val)
        
            # But a tool is also a node, at least in the sense that it has a data container. So, we could
            # also store things there. We could even be a bit extra clever and just store the whole tool
            # container inside the tool container.
            doc.SetAction(200000089)
            tdata: c4d.BaseContainer = doc.GetActiveToolData()
        
            print ("Current Tool Container State:")
            PrintContainer(tdata)
            print ("Past Tool Container State:")
            PrintContainer(tdata[ID_DATA_STORAGE] or c4d.BaseContainer())
        
            copy: c4d.BaseContainer = tdata.GetClone(c4d.COPYFLAGS_NONE)
            copy.RemoveData(ID_DATA_STORAGE)
            tdata[ID_DATA_STORAGE] = copy
        

        Example output:

        The last object message is:  bgafgafbfd
        Setting new message: befdbeeadc
        Current Tool Container State:
        705 1
        20020 0
        1061998 <c4d.BaseContainer object at 0x000001D978954040>
        707 None
        Past Tool Container State:
        705 1
        20020 0
        707 None
        

        I'm also not sure whether this shared-axis-behavior is intentional?

        I am not quite sure how this is meant ? You mean that the default value for Per-Object-Transform should be True? Such questions/whishes should be directed towards end-user support, as we in SDK have nothing to do with that.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        CJtheTigerC 1 Reply Last reply Reply Quote 1
        • CJtheTigerC
          CJtheTiger @ferdinand
          last edited by

          Hi @ferdinand,

          Thank you for another great response.

          Yes, my question is indeed about how to reset the tool to the state it had before my script tinkered with it. I was hoping there is an easy way to do this judging by the doc paragraph I quoted, but it sounds like there isn't. I'll try and be clearer about my question in the future.


          Your two suggestions both seem fine, so thank you very much for those!

          Being responsible with what we know is part of what makes this job fun. 🙂


          This here:

          I'm also not sure whether this shared-axis-behavior is intentional?

          was aimed towards the behavior and intention of the Interaction Tag, not towards the default value of the tool flag. Let me explain:

          From my understanding one of the purposes of the Interaction Tag is to manipulate the object Proxy through the object Controller which holds the Interaction Tag. Since the tag effectively prevents the Controller from being manipulated and instead directs all of this to Proxy I believe that the origin of the manipulation matrix should not be in the middle of Proxy and Controller (which it is as you can see in my previous gif) but directly on the object axis of Proxy instead.

          But if this should be a support ticket instead of a forum discussion I can go ahead and do that of course, I just figured you might know of a good reason why the way it is may actually be intended.

          Cheers,
          Daniel

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @CJtheTiger
            last edited by ferdinand

            Hey @CJtheTiger,

            just as a clarification, it is obvious that you have put quite a bit of effort in your posting. So, that was not meant in the sense of "what a terrible posting". But especially for postings which contain a lot of detail, it is important to put the question at the very beginning so that it is clear what is the question.

            Regarding the axis behavior thing, I now understand how you mean that. The Interaction Tag (and Tooling) is not owned by the SDK group, so we would not be responsible for this case either, we only own all the "pure" Python stuff. What I thought was your request before, changing the general default value, had probably almost zero changes of being implemented. This request of yours sounds logical (I am not a big expert on the interaction tag) but given how niche that case is, and that it would require customization in tools just for that case, I do not see a high chance that this will ever be implemented either.

            But if you truly desire that feature, you should still submit the wish, because a lot of user requests for the same thing are something we cannot and will not ignore.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • First post
              Last post