Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Connect C4D with Tkinter (External Application) ?

    Cinema 4D SDK
    r21 python
    3
    12
    2.3k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      bentraje
      last edited by

      @r_gigante

      Thanks for the response. I added c4d.SpecialEventAdd() and the CoreMessage 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()
      
      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        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)
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 1
        • r_giganteR
          r_gigante
          last edited by r_gigante

          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

          1 Reply Last reply Reply Quote 1
          • ferdinandF
            ferdinand
            last edited by

            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

            MAXON SDK Specialist
            developers.maxon.net

            r_giganteR 1 Reply Last reply Reply Quote 2
            • B
              bentraje
              last edited by

              @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=0

              I 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 the SpecialEventAdd() and CoreMessage run every time and not only once?

              1 Reply Last reply Reply Quote 0
              • r_giganteR
                r_gigante
                last edited by r_gigante

                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

                1 Reply Last reply Reply Quote 3
                • r_giganteR
                  r_gigante @ferdinand
                  last edited by

                  @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

                  1 Reply Last reply Reply Quote 0
                  • B
                    bentraje
                    last edited by

                    Hi @r_gigante

                    Thanks for the response. There is an value error. I tried to solve it but the Cthing is still over my head 😞
                    It's on line P1MSG_EN1 = pythonapi.PyCapsule_GetPointer(P1MSG_UN1, None)
                    with the error of ValueError: PyCapsule_GetPointer called with invalid PyCapsule object

                    Just wondering, why do we need to convert the data into C then back into Python ?

                    1 Reply Last reply Reply Quote 0
                    • r_giganteR
                      r_gigante
                      last edited by

                      Hi @bentraje: the code is meant for R23, with python 3: please make have a look at the changes described here to revert the changes to python 2.

                      Cheers, R

                      1 Reply Last reply Reply Quote 1
                      • B
                        bentraje
                        last edited by

                        @r_gigante

                        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.

                        1 Reply Last reply Reply Quote 0
                        • ferdinandF ferdinand referenced this topic on
                        • ferdinandF ferdinand referenced this topic on
                        • First post
                          Last post