Reset Tool in Interaction Tag
-
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:
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
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 toTrue
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=iwNum5To see the behavior:
- Activate the Scale tool.
- Set the flag Per-Object-Transform to
True
. - Activate another tool (for example move or rotate).
- Make sure no object is selected, then right-click-drag the sphere which will scale the cube.
- 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 -
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:
- 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)
- 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 -
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 -
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