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)