SOLVED Get and Set Move Tool's XYZ Axis Lock State

Hi,

How can I get and set Move Tool's XYZ Axis Lock state? when I click the buttons, the Script Log display as:

c4d.CallCommand(12153) # X-Axis / Pitch
c4d.CallCommand(12154) # Y-Axis / Heading
c4d.CallCommand(12155) # Z-Axis / Bank

axis lock.png

I append an Interaction Tag on a object, when I select this object, I want it automaticly change to Move Tool and lock the Y axis and Z axis,
when I deselect the object, it restore the original axis lock state, then change back to last active tool.

Here is the code I put in the tag:

import c4d
from c4d import gui

def onSelect():
    #Code for what happens when the object gets selected goes here
    global last_tool
    last_tool = doc.GetAction() # store the active tool
    
    c4d.CallCommand(c4d.ID_MODELING_MOVE) # change to Move Tool
    gui.ActiveObjectManager_SetObject( # change Attribute Manager to Object Mode
        c4d.ACTIVEOBJECTMODE_OBJECT, op, c4d.ACTIVEOBJECTMANAGER_SETOBJECTS_OPEN)

    # TODO: Get Move Tool current axis lock state, then change the state

def onDeselect():
    #Code for what happens when the object gets unselected goes here

    # TODO: Restore Move Tool's last axis lock state

    doc.SetAction(last_tool) # restore the last active tool

#def onHighlight():
    #Code for what happens when the object gets highlighted goes here

#def onUnhighlight():
    #Code for what happens when the object gets unhighlighted goes here

by the way, what is the difference between "onSelect" and "onHighlight"?

Thanks!

The status-test equivalent to CallCommand(12153) is c4d.IsCommandChecked(12153) etc.

Thanks @cairyn! When i read c4d.IsCommandChecked(id), it says Forbidden to call in expressions (Python generator, tag, XPresso node, etc.). Is it safe to use this in a Interaction Tag?

@eziopan said in [Python] Get and Set Move Tool's XYZ Axis Lock State:

Thanks @cairyn! When i read c4d.IsCommandChecked(id), it says Forbidden to call in expressions (Python generator, tag, XPresso node, etc.). Is it safe to use this in a Interaction Tag?

That is an interesting question; I guess the Maxon people will know for sure. The tags mentioned are all called during an evaluation of the object tree, which is a threaded operation, which forbids certain operations. The interaction tag responds to user interaction, which IMHO is different? If the script in an interaction tag is called in the main thread, you could use these commands as well.

I just tried and used IsCommandChecked and CallCommand in an interaction tag in the mouseDown() callback, and it seems to work; at least C4D didn't crash. That's no guarantee that it will never crash, of course, so I defer to Maxon specialists in the matter 🙂

As far as the Select/Highlight issue goes, I find myself unable to trigger the onHighlight callback properly. The help says "This is called when the object is highlighted by the mouse cursor." but no, it isn't. When I click the right mouse button over the object and drag it so the menu doesn't appear, I get the onHighlight console printout after releasing, but only in Model Mode so far. This is a fairly mysterious message. ("Highlight Object" needs to be checked; but even so - I don't see any highlight...)

Thanks again @cairyn!
I will use the method your introduced, and keep this topic open, hoping someone could make this more clearly.

Hello @eziopan

thank you for reaching out to us. And thank you @Cairyn for providing an answer.

The warning note is there as certain things are not allowed to be done outside from the main thread, for details see Threading Information. Functionalities that are only intended to be used from the main thread will either refuse execution all-together outside form the main thread, like for example some redraw related stuff. But many of them will actually run, but the user might interfere with the program state and main loop in the main thread, causing Cinema 4D to crash in the worst case.

There are some methods which are called very often outside from the main thread like ObjectData.GetVirtualObjects, TagData.Execute, or ShaderData.Output. And there are some like NodeData.Message where you have a relatively good chance for being inside the main thread when they are called. This transfers to scripting object analogs of these methods, e.g., for the Python Programming Tag one can expect main() (equivalent of TagData.Execute) to run outside of and message (equivalent of NodeData.Message) inside of the main thread. But these are not guarantees, and one must check with c4d.threading.GeIsMainThread() if one is on the main thread or not, if one wants to rely on it. I have tried it out for the interaction tag, and most of its functions are called from the main thread when I tried it (except for some calls to draw). See example_1 at the end for details.

But this is still just what happend in my case in this one instance, and in the end, there is nothing which prevents some other entity calling C4DAtom.Message on your interaction tag from outside of the main thread and therefore pass this problem down to the message() function inside the tag. The best pattern to avoid these problems is to cache/delay such access or invoking of main-thread related stuff. See example_2 for details at the end.

Cheers,
Ferdinand

example_1 code:

import c4d

def mouseDown():
    print (f"mouseDown in main thread: {c4d.threading.GeIsMainThread()}")

def mouseDrag():
    print (f"mouseDrag in main thread: {c4d.threading.GeIsMainThread()}")

def mouseUp():
    print (f"mouseUp in main thread: {c4d.threading.GeIsMainThread()}")

def doubleClick():
    print (f"doubleClick in main thread: {c4d.threading.GeIsMainThread()}")

def onSelect():
    print (f"onSelect in main thread: {c4d.threading.GeIsMainThread()}")

def onDeselect():
    print (f"onDeselect in main thread: {c4d.threading.GeIsMainThread()}")

def onHighlight():
    print (f"onHighlight in main thread: {c4d.threading.GeIsMainThread()}")

def onUnhighlight():
    print (f"onUnhighlight in main thread: {c4d.threading.GeIsMainThread()}")

def draw(bd):
    print (f"draw in main thread: {c4d.threading.GeIsMainThread()}")

def message(id, data):
    print (f"message in main thread: {c4d.threading.GeIsMainThread()}")

example_1 output (I got):

mouseDown in main thread: True
mouseDrag in main thread: True
mouseUp in main thread: True
doubleClick in main thread: True
onSelect in main thread: True
onDeselect in main thread: True
onHighlight in main thread: True
onUnhighlight in main thread: True
draw in main thread: True
message in main thread: True
mouseDown in main thread: True
mouseDrag in main thread: True
mouseUp in main thread: True
doubleClick in main thread: True
onSelect in main thread: True
onDeselect in main thread: True
onHighlight in main thread: True
onUnhighlight in main thread: True
draw in main thread: True
message in main thread: True
mouseDown in main thread: True
mouseDrag in main thread: True
mouseUp in main thread: True
doubleClick in main thread: True
onSelect in main thread: True
onDeselect in main thread: True
onHighlight in main thread: True
onUnhighlight in main thread: True
draw in main thread: True
message in main thread: True
[more message calls]
draw in main thread: False
message in main thread: True
[more message calls]
draw in main thread: False

example_2 code:

"""Example for deferring an execution to the main thread.

As discussed in:
    https://plugincafe.maxon.net/topic/13481/
"""

import c4d

# Plugin ID of mine which I can guarantee is not used in your documents, 
# please register your own ID.
STORAGE_ID = 1057432
# Does not have to be a plugin id, since we use it inside our own BaseContainer
ID_AXIS_LOCK_STATE = 0
                    
def mouseDown():
    """Here we are accessing the data.
    """
    # Retrieve a copy of the tags container.
    tagContainer = tag.GetData()
    # Retrieve the data at STORAGE_ID.
    bc = tagContainer[STORAGE_ID]
    # Check if its "our stuff".
    if isinstance(bc, c4d.BaseContainer) and bc.GetId() == STORAGE_ID:
        # Print the state.
        print (f"Axis Lock State is: {bc[ID_AXIS_LOCK_STATE]}")

def message(id, data):
    """We just use message as a place where we assume often to be on the
    main thread.
    """
    # Ensure we are on the main thread, since it is not guaranteed, even in
    # message.
    if c4d.threading.GeIsMainThread():
        # Do whatever we want to do.
        state = c4d.IsCommandChecked(12153)

        # We could store state as a global variable or inject it into the
        # Python interpreter, but the former would make us dependent on the
        # volatility of the script module and the latter is not a great idea
        # either, although due to c4d.threading.GeIsMainThread() we would
        # ensure avoiding access problems.

        # Instead we are going to store the data in the tag. For that we
        # need unique plugin ID from plugin Café to reasonably well avoid id
        # collisions. We also give the container itself that ID so that we
        # can easily identify it as a container created by us.
        bc = c4d.BaseContainer(STORAGE_ID)
        bc[ID_AXIS_LOCK_STATE] = state

        # tag is predefined as the incarceration tag inside an interaction tag
        # script. We retrieve its data container instance.
        tagContainer = tag.GetDataInstance()

        # We retrieve the content in the tag's data container at STORAGE_ID
        # and when it is not None or a BaseContainer with the ID STORAGE_ID,
        # we bail, as something else is storing something there. Unlikely to
        # happen, but better safe than sorry.
        content = tagContainer[STORAGE_ID]
        is_container = isinstance(content, c4d.BaseContainer)
        # Bail if not a BaseContainer written by us.
        if is_container and content.GetId() != STORAGE_ID:
            return
        # Bail when not a BaseContainer and not None.
        elif not is_container and content is not None:
            return
        # Otherwise write our bc to the data container of the tag.
        else:
            tagContainer.SetContainer(STORAGE_ID, bc)

Thank you @ferdinand for the detailed answer and code.
I will do more reading in the sdk document.