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
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
andCallCommand
in an interaction tag in themouseDown()
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 matterAs 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
, orShaderData.Output
. And there are some likeNodeData.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 expectmain()
(equivalent ofTagData.Execute
) to run outside of andmessage
(equivalent ofNodeData.Message
) inside of the main thread. But these are not guarantees, and one must check withc4d.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). Seeexample_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 themessage()
function inside the tag. The best pattern to avoid these problems is to cache/delay such access or invoking of main-thread related stuff. Seeexample_2
for details at the end.Cheers,
Ferdinandexample_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://developers.maxon.net/forum/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.