Connect C4D with Tkinter (External Application) ?
-
Thanks for the response. I added
c4d.SpecialEventAdd()
and theCoreMessage
on the GeDialog().
I expect when I click the tkinter button to print "Hey I am listening!" but I unfortunately get nothing.Here is the revised code:
import c4d import socket from c4d.threading import C4DThread thread_ = None host, port = '127.0.0.1', 12121 pluginID = 131313 pluginIDCommandData = 141414 def create_primitive(primitive_type): if primitive_type == 'cube': doc.InsertObject(c4d.BaseObject(c4d.Ocube)) if primitive_type == 'sphere': doc.InsertObject(c4d.BaseObject(c4d.Osphere)) if primitive_type == 'plane': doc.InsertObject(c4d.BaseObject(c4d.Oplane)) c4d.EventAdd() #Background_Server is driven from Thread class in order to make it run in the background. class BGThread(C4DThread): end = False # Called by TestBreak to adds a custom condition to leave def TestDBreak(self): return bool(self.end) #Start the thread to start listing to the port. def Main(self): self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket_.bind((host, port)) while True: if self.TestBreak(): return self.socket_.listen(5) client, addr = self.socket_.accept() data = "" buffer_size = 4096*2 data = client.recv(buffer_size) create_primitive(primitive_type=data) c4d.SpecialEventAdd(pluginID, 0, 0) class DialogSetting(c4d.gui.GeDialog): def CreateLayout(self): self.SetTitle("Primitive Plugin") self.GroupBegin(id=1, flags=c4d.BFH_SCALEFIT, rows=3, title=("Primitive Plugin"), cols=1, initw = 500) self.AddCheckbox(1001, c4d.BFH_CENTER, 300, 10, "Settings 1") self.AddCheckbox(1002, c4d.BFH_CENTER, 300, 10, "Settings 2") self.AddCheckbox(1003, c4d.BFH_CENTER, 300, 10, "Settings 3") self.GroupEnd() return True def CoreMessage(self, id, msg): if id == pluginID: c4d.StopAllThreads() print ("Hey I am listening!") c4d.EventAdd() return True class CommandDataDlg(c4d.plugins.CommandData): dialog = None def Execute(self, doc): if self.dialog is None: self.dialog = DialogSetting() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=pluginIDCommandData, xpos=-1, ypos=-1, defaultw=200, defaulth=150) def RestoreLayout(self, sec_ref): # manage the dialog if self.dialog is None: self.dialog = DialogSetting() return self.dialog.Restore(pluginid=pluginIDCommandData, secret=sec_ref) def PluginMessage(id, data): global thread_ # At the start of Cinema 4D We lunch our thread if id == c4d.C4DPL_PROGRAM_STARTED: thread_ = BGThread() thread_.Start() def main(): c4d.plugins.RegisterCommandPlugin(id=pluginIDCommandData, str="Primitive Plugin", help="Primitive Plugin", info=0, dat=CommandDataDlg(),icon=None) # Execute main() if __name__=='__main__': main()
-
Hi,
I am not the biggest tkinter expert, but your file above seems to have various problems. Here is a commented version instead of me trying to explain everything in a gigantic paragraph. As stated in the code below, my comments are a bit on point and could be construed as harsh or insulting, which is not my intention.
Cheers,
zipit"""I am following here more or less a "the gloves are off approach" in commenting. I do not intend to be rude and it could very well be that I did miss the trick here. But this approach is just way more efficient than being super defensive in my language. This is meant to be constructive. """ from tkinter import * import socket window = Tk() # Python is typeless so this initialization does not make much sense. data = bytes('', "utf-8") def send_msg(msg): """ """ # You are writing here to the variable 'data' in the scope of send_msg, # not to the module attribute of the same name. To shadow this local # variable with the global attribute, you would have to use the global # keyword, like so: # global data data = bytes(msg, "utf-8") # But the keyword global is the devils work and should not be used. # Clicking these buttons won't do anything since everything they do is # to raise send_msg, which is flawed in itself. b1 = Button (window, text="Cube", command=send_msg("cube")) b1.grid(row=0, column=0) b1 = Button (window, text="Sphere", command=send_msg("sphere")) b1.grid(row=1, column=0) b1 = Button (window, text="Plane", command=send_msg("plane")) b1.grid(row=2, column=0) # This is blocking, i.e. the following lines won't be reached until the # event loop of 'window' has finished. It basically works like any other # GUI framework under the sun. window.mainloop() # Only reached after the window has closed. host, port = '127.0.0.1', 12121 socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socket.connect((host, port)) # This does not make much sense to me either. First of all this is a loop # without an exit condition. But even if we ignore this and the fact that # the whole data thing above does not work and and the event loop of the # window is blocking, this would only send over and over the last message, # i.e. the last button that has been clicked before the app/window has # been closed. while True: socket.sendall(data)
-
Thanks @zipit for jumping on the discussion.
Actually @bentraje's client code looks a bit wonky and I think it should be refactored in something like:
import tkinter, socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 12121)) window = tkinter.Tk() def SendMessage(msg): client.send(bytes(msg, "utf-8")) print(msg) tkinter.Button(window, text = "Cube", command = lambda:SendMessage("cube")).pack() tkinter.Button(window, text = "Sphere", command = lambda:SendMessage("sphere")).pack() tkinter.Button(window, text = "Plane", command = lambda:SendMessage("plane")).pack() window.mainloop() client.close()
Best, R
-
Hi,
yeah, that could be a way to do it. I personally would do a few things differently though. One might want to consider that also connections to
localhost
can be rejected and even when a connection has been established, messages still can get lost. And I would also always prefer sending integers over strings when ever possible. Implementing the whole thing as a class also seems much more reasonable.Cheers,
zipit -
@r_gigante @zipit
Thanks for the response. Yea, I guess the connection was never made since the socket creation was made after
mainloop
I tried the
tkinter code
provided by @r_gigante.
It works, the problem is it only works once. So after clicking next buttons, primitives are not created.
You can see it here:
https://www.dropbox.com/s/m2mpk2ctjkizgfc/c4d308_python_socket02.mp4?dl=0I can see that the tkinter button works (i.e. prints out the "cube", "plane" etc on every click).
I guess the problem is the c4d plug-in receives it only once.
Is there a way to have theSpecialEventAdd()
andCoreMessage
run every time and not only once? -
Hi @bentraje, I've spent a few hours on researching a solution which could look something like:
Tkinter client-sde
import tkinter, socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 1234)) window = tkinter.Tk() def SendMessage(msg): client.send(bytes(msg, "utf-8")) print(msg) tkinter.Button(window, text = "Cube", command = lambda:SendMessage("create a cube")).pack() # 'command' is executed when you click the button tkinter.Button(window, text = "Sphere", command = lambda:SendMessage("create a sphere")).pack() # 'command' is executed when you click the button window.mainloop() client.close()
C4D server-side
import c4d import socket from ctypes import pythonapi, c_int, py_object from c4d.threading import C4DThread _thread = None pluginIDMessageData = 151515 class Listener(C4DThread): _stopThread = False _conn = None _addr = None _socket = None def OpenSocket(self): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.bind(('127.0.0.1', 1234)) self._socket.listen(5) return (self._socket is not None) def CloseSocket(self): # close connection if self._conn is not None: self._conn.close() # close socket if self._socket is not None: self._socket.close() # signal the thread self._stopThread = True # called by TestBreak to adds a custom condition to leave def TestDBreak(self): return self._stopThread def Main(self): # check for connections while not self.TestBreak(): self._conn, self._addr = self._socket.accept() while not self.TestBreak(): #check for data data = self._conn.recv(4096) if not data: break if (data.decode('utf-8') == "create a cube"): c4d.SpecialEventAdd(1234, 1, 0) elif (data.decode('utf-8') == "create a sphere"): c4d.SpecialEventAdd(1234, 2, 0) # close connection and notify self._conn.close() class MyMessageData(c4d.plugins.MessageData): def CoreMessage(self, id, bc): if id == 1234: # convert the message pythonapi.PyCapsule_GetPointer.restype = c_int pythonapi.PyCapsule_GetPointer.argtypes = [py_object] P1MSG_UN1 = bc.GetVoid(c4d.BFM_CORE_PAR1) P1MSG_EN1 = pythonapi.PyCapsule_GetPointer(P1MSG_UN1, None) # check message and act if (P1MSG_EN1 == 1): c4d.documents.GetActiveDocument().InsertObject(c4d.BaseObject(c4d.Ocube)) elif (P1MSG_EN1 == 2): c4d.documents.GetActiveDocument().InsertObject(c4d.BaseObject(c4d.Osphere)) c4d.EventAdd() return True def PluginMessage(id, data): global _thread # At the start of Cinema 4D We lunch our thread if id == c4d.C4DPL_PROGRAM_STARTED: _thread = Listener() if _thread.OpenSocket(): _thread.Start(c4d.THREADMODE_ASYNC, c4d.THREADPRIORITY_LOWEST) return True if id == c4d.C4DPL_ENDACTIVITY: if (_thread): _thread.CloseSocket() _thread.End() return True def main(): c4d.plugins.RegisterMessagePlugin(id=pluginIDMessageData, str="", info=0, dat=MyMessageData()) # Execute main() if __name__=='__main__': main()
Cheers, R
-
@zipit I agree with all your points but for brevity I decided to present a code that would not differ too much from @bentraje initial post.
So far thanks for your remarks!
Cheers, R
-
Hi @r_gigante
Thanks for the response. There is an value error. I tried to solve it but the
C
thing is still over my head
It's on lineP1MSG_EN1 = pythonapi.PyCapsule_GetPointer(P1MSG_UN1, None)
with the error ofValueError: PyCapsule_GetPointer called with invalid PyCapsule object
Just wondering, why do we need to convert the data into C then back into Python ?
-
-
Thanks for the response and the website reference.
I was able to work the code with the following revisions:def CoreMessage(self, id, bc): if id == 1234: P1MSG_UN = bc.GetVoid(c4d.BFM_CORE_PAR1) pythonapi.PyCObject_AsVoidPtr.restype = c_int pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object] P1MSG_EN = pythonapi.PyCObject_AsVoidPtr(P1MSG_UN) # check message and act if (P1MSG_EN == 1): c4d.documents.GetActiveDocument().InsertObject(c4d.BaseObject(c4d.Ocube)) elif (P1MSG_EN == 2): c4d.documents.GetActiveDocument().InsertObject(c4d.BaseObject(c4d.Osphere))
Thanks again. Will close this thread now.
-
-