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 implementation CommandData
with an implementation of CommandData.Message
attached to it. CommandData
is derived from c4d.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 a c4d.plugins.BasePlugin
. So this is not an instance of your MyPlugin
implementation, but a surrogate for it. This is not something that only does happen for CommandData
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 example ShaderData
and the tangible interface layer is a BaseShader
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()
on MyShaderInstanceObj
then you are not calling directly MyShaderData.Message()
, but Message()
on a BaseList2D
which is representing your implementation. But for a node, Cinema 4D will pipe behind the scenes the call MyShaderInstanceObj.Message()
to MyShaderData.Message()
. For a CommandData
plugin that is not the case. When you invoke c4d.plugins.FindPlugin(1057321).Message(123)
, c4d.plugins.FindPlugin(1057321)
will be a BasePlugin
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 generic BaseList2D
, 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
and MSG_RETRIEVEPRIVATEDATA
. The problem is - and I just tried it - that Cinema 4D won't propagate these from a BasePlugin
representing a CommandData
to the CommandData.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 the CommandData
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 a MyCommandDataImplementation
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 a MessageData
plugin instead of a GeDialog
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,
Ferdinand
The implementation:
"""Example for using core messages to send messages to a CommandData
implementation.
As discussed in:
https://plugincafe.maxon.net/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 a MessageData
plugin.
import c4d
ID_MYPLUGIN = 1057346
# Sends the message data `1`
c4d.SpecialEventAdd(ID_MYPLUGIN, 1)