Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
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 ?
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)
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.
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.
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.
MyPlugin
CommandData
CommandData.Message
c4d.plugins.BaseData
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.
c4d.plugins.FindPlugin
c4d.plugins.BasePlugin
ShaderData
BaseShader
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.".
Message()
MyShaderInstanceObj
MyShaderData.Message()
BaseList2D
MyShaderInstanceObj.Message()
c4d.plugins.FindPlugin(1057321)
BasePlugin
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.
MSG_BASECONTAINER
MSG_RETRIEVEPRIVATEDATA
So long story short: You cannot really send custom messages to a CommandData.Message(). You can:
CommandData.Message()
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).
GeDialog
MyCommandDataImplementation
MessageData
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)
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