Send Message to CommandData
-
Hello,
I try to send a message to a CommandData plugin like this :
class MyPlugin(plugins.CommandData): def Message(self, type, data): print("test", type, data) return True if __name__ == "__main__" : plugins.RegisterCommandPlugin(id=1057321, str="test", info=0, help="", dat=MyPlugin(), icon=None )
And then send the message like this in the console :
c4d.plugins.FindPlugin(1057321).Message(123)
It returns True but nothing print. Do you know why ?
-
Not sure if this the reason but according to the documentation the Message from CommandData responds to messages of type MSG_COMMAND. Since you're sending a message with type 123, I can only imagine the CommandData does not react to this type of message.
What I have done in the past to trigger a CommandData is use the ExecuteSubID.
I define a value for a custom message, making sure to use a value higher than ID_TREEVIEW_FIRST_NEW_ID
and perform a CallCommand(<command-pluginID>, <custom-message-value>).
In the CommandData I then react to the specific custom-message-value passed in ExecuteSubID.
Granted, there might be other workarounds to message a CommandData, but the way I have done worked for me.Note: Make sure to use a value higher than ID_TREEVIEW_FIRST_NEW_ID in order not to conflict with internal values defined for menus and handled in the CommandData GetSubContainer
-
Mm, it did not work for me, the message was still not triggered even with a message ID higher than ID_TREEVIEW_FIRST_NEW_ID.
I managed in a different way with PluginMessage and c4d.GePluginMessage
But contrarely to what say doc, it's not the easiest way if you want to pass some data lol, the data received from PluginMessage is not an object but a PyCapsule object.
Here is how I retreived the original data object, it's not very elegant but it works :
import ctypes import _ctypes def getObjectFromCapsule(capsule): ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p] pointer = ctypes.pythonapi.PyCapsule_GetPointer(capsule, None) return _ctypes.PyObj_FromPtr(pointer) def PluginMessage(id, data): if id == MY_UNIQUE_ID : o = getObjectFromCapsule(data) o['foo'] = 'bar' return True return False
And then in my other plugin :
o = {} print(c4d.GePluginMessage(MY_UNIQUE_ID, o)) # {'foo': 'bar'}
-
@césar-vonc said in Send Message to CommandData:
Mm, it did not work for me, the message was still not triggered ...
Sorry to have confused you.
The solution I mentioned was not to trigger the Message function of a CommandData. But the explicitly call its ExecuteSubID. -
Here is some example code of what I mean:
# Python 2.7 import c4d from c4d import plugins from c4d import bitmaps import os CMD_PLUGIN_ID = 1000001 TRIGGER_PLUGIN_ID = 1000002 kBaseMessageID = c4d.ID_TREEVIEW_FIRST_NEW_ID; kTriggerMessageID = kBaseMessageID + 1 kAnotherTriggerMessageID = kBaseMessageID + 2 # ... etc # =============== CommandData class ============= class TestCommandData(c4d.plugins.CommandData): def Execute(self, doc): return True def ExecuteSubID(self, doc, subid): if subid == kTriggerMessageID: # we got triggered .. do something print "TestCommandData was trigger for ", subid return True class TriggerCommandData(c4d.plugins.CommandData): def Execute(self, doc): print "TriggerCommandData calls TestCommandData.ExecuteSubID with value", kTriggerMessageID c4d.CallCommand(CMD_PLUGIN_ID, kTriggerMessageID) return True # =============== Main ============= def PluginMain(): bmp = c4d.bitmaps.BaseBitmap() dir, file = os.path.split(__file__) fn = os.path.join(dir, "res", "test.png") bmp.InitWith(fn) plugins.RegisterCommandPlugin( id=CMD_PLUGIN_ID, str="TestCommand", info=0, icon=bmp, help="TestCommand", dat=TestCommandData()) plugins.RegisterCommandPlugin( id=TRIGGER_PLUGIN_ID, str="TriggerCommand", info=0, icon=bmp, help="TriggerCommand", dat=TriggerCommandData()) return if __name__ == "__main__": PluginMain()
The above registers 2 CommandData plugins: "TestCommandData" and " TriggerCommandData".
When you select the "TestCommandData" from the plugin menu -> nothing happens.
When you select the "TriggerCommandData" it will call the "TestCommandData" with a subid.
This mimics sending the CommandData a message.
In TestCommandData.ExecuteSubID you then react to the particular "custom message ID". You can have as many of these as you want, and can react differently to each.NOTE: the source of the trigger does not need to be another CommandData, it can be anything you like. The only thing you need to do is to perform a c4d.CallCommand(<pluginid>, <trigger-messageid>) from where you want to trigger your CommandData, and provide the trigger-messageid you want the CommandData to react on.
Now, if you also want to pass data ... that's another story, indeed.
Maybe have a look at c4d.SpecialEventAdd (but I guess this is a CoreMessage which does only get sent to MessageData type of plugins) -
Aah ok, I misunderstood indeed. Thanks a lot for your complete answer.
I take a look on all of this. -
Hello @César-Vonc,
thank you for reaching out to us and thank you @C4DS for taking the time to answer the question. I think the solution by @C4DS is quite clever, and I do not see speaking much against it. But let us unpack some other stuff first.
@césar-vonc said in Send Message to CommandData:
class MyPlugin(plugins.CommandData)
MyPlugin
is an implementationCommandData
with an implementation ofCommandData.Message
attached to it.CommandData
is derived fromc4d.plugins.BaseData
. Which is just a dummy interface to bind all the plugin interfaces together, i.e., does nothing from a user point of view.@césar-vonc said in Send Message to CommandData:
c4d.plugins.FindPlugin(1057321).Message(123)
c4d.plugins.FindPlugin
however, will return ac4d.plugins.BasePlugin
. So this is not an instance of yourMyPlugin
implementation, but a surrogate for it. This is not something that only does happen forCommandData
plugins, but for all plugin types: They are separated into an implementation layer and an interface layer. For instantiable plugin types this is a bit more straight forward, as the implementation layer is then for exampleShaderData
and the tangible interface layer is aBaseShader
floating around somewhere in a document. It is an intentional design that the user has no access to the implementation layer, but only access to the interface layer. For a shader, or any other kind of node, this means that you are restricted to sending messages to the implementation, while the implementation has full access to the interface layer, to the node(s) which make use of that implementation.When you call
Message()
onMyShaderInstanceObj
then you are not calling directlyMyShaderData.Message()
, butMessage()
on aBaseList2D
which is representing your implementation. But for a node, Cinema 4D will pipe behind the scenes the callMyShaderInstanceObj.Message()
toMyShaderData.Message()
. For aCommandData
plugin that is not the case. When you invokec4d.plugins.FindPlugin(1057321).Message(123)
,c4d.plugins.FindPlugin(1057321)
will be aBasePlugin
which then says "Message ID 123, never heard of it, next pls.".Which brings us to the second problem. The name space for node messages is curated by Maxon. Which means that you cannot just send any message ID unless you get hold of the specific object you want to send messages to (which is in most cases impossible due to the separation of layers). Cinema 4D will simply not propagate any messages that are not defined in the SDK. I.e., you can send them to a node, usually some
BaseList2D
in a scene graph, but the message chain will then immediately stop with that node. And due to that node being just some genericBaseList2D
, it will then also not know what to do with that message.For nodes there some message IDs which are intended to be used in such cases, and due to being defined by Maxon will also be propagted through the message chain. Most notably
MSG_BASECONTAINER
andMSG_RETRIEVEPRIVATEDATA
. The problem is - and I just tried it - that Cinema 4D won't propagate these from aBasePlugin
representing aCommandData
to theCommandData.Message
implementation.So long story short: You cannot really send custom messages to a
CommandData.Message()
. You can:- Use @C4DS quite elegant work-around.
- Use core messages when you have an opened dialog.
- Shove data around "manually" within the visibility scope of the implementation.
Option two requires an opened plugin
GeDialog
which receives core messages. Due to having direct access to theCommandData
implementation, you then can pipe these messages through. See example at the end for details. Option three just means that within your implementation you can push data around as please. So everything that knows what aMyCommandDataImplementation
is, or even knows the concrete instance of it, can communicate with it. This would also allow for a more complex version of option two, ussing aMessageData
plugin instead of aGeDialog
for recieving the core messages (and therefore does not require an opened dialog).A lot of text for very little help But I hope this clears a bit up why this is not working.
Cheers,
FerdinandThe implementation:
"""Example for using core messages to send messages to a CommandData implementation. As discussed in: https://developers.maxon.net/forum/topic/13316/ """ import c4d ID_INT_VALUE = 1000 class PluginDialog (c4d.gui.GeDialog): """A dialog for a CommandData plugin. """ def __init__(self, host): """The dialog is being inited with a binding to the hosting plugin instance. """ if not isinstance(host, c4d.plugins.CommandData): raise TypeError(f"Expected an instance of CommandData as 'host'. " f"Received: {type(host)}") self._host = host def CoreMessage(self, mid, mdata): """We react to core messages under our plugin ID and pipe them though to the CommandData implementation. """ if mid == self._host.ID_PLUGIN: self._host.Message(mid, mdata) return c4d.gui.GeDialog.CoreMessage(self, mid, mdata) class DialogCommandData(c4d.plugins.CommandData): """A CommandData plugin with a dialog. """ ID_PLUGIN = 1057346 def __init__(self): """ """ self._pluginDialog = None def GetPluginDialog(self): """Returns the instance bound instance of the plugin dialog. Usually I like CommandData plugin dialogs to be bound to the class and not the instance, i.e., this then would be a class method. But for this approach we have to attach the dialog to an instance, since we need to pass it to the dialog, so that it can call Message() - which is not a classmethod, i.e., requires an instance reference. """ if self._pluginDialog is None: self._pluginDialog = PluginDialog(host=self) return self._pluginDialog def Message(self, mid, mdata): """React to messages. """ # We receive a core message from the dialog. if mid == DialogCommandData.ID_PLUGIN: print (mid, mdata) return True def ExecuteOptionID(self, doc, plugid, subid): """Opens the dialog. """ result = True dialog = self.GetPluginDialog() if not dialog.IsOpen(): result = dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=DialogCommandData.ID_PLUGIN) return result def RestoreLayout(self, secret): """Restores the dialog on layout changes. """ dialog = self.GetPluginDialog() return dialog.Restore(DialogCommandData.ID_PLUGIN, secret) if __name__ == '__main__': myPlugin = DialogCommandData() c4d.plugins.RegisterCommandPlugin( id=DialogCommandData.ID_PLUGIN, str="CommandData Message Examples", info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG, icon=None, help="CommandData Message Examples.", dat=myPlugin)
You can then send a core message to it from anywhere like shown below. But for this to work, the
GeDialog
must be opened and visible, otherwise you need the more complex approach with aMessageData
plugin.import c4d ID_MYPLUGIN = 1057346 # Sends the message data `1` c4d.SpecialEventAdd(ID_MYPLUGIN, 1)
-
Hello @César-Vonc,
without any further questions or replies, we will consider this topic as solved by Wednesday and flag it accordingly.
Thank you for your understanding,
Ferdinand