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

    ToolData Linkbox does not retain object when one is dragged in.

    Bugs
    python
    2
    7
    1.6k
    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
      BretBays
      last edited by

      Howdy,
      I found myself with some time to look at my plugin I was working on. It is a ToolData plugin that uses AllocSubDialog to call my subDialogClass. In my subDialog class, I am using createLayout to make a linkbox customGUI element via the following line in my CreateLayout function

      self.linkGadget = self.AddCustomGui(9898, c4d.CUSTOMGUI_LINKBOX, "Custom", c4d.BFH_SCALEFIT, minw=100, minh=10)
      

      In terms of drawing my UI, it all works. I see my link box no problemo. I began to try to do some GetLinkObject type functions to query the field and return it's value. That seems to be working, however I can't fully test it because I cannot seem to actually drag and drop my opject into the link field and have it stick.

      I feel like this is something pertaining to the messaging and stuff of a toolData plugin? I have used link fields many times in CommandData plugins, but this is my first toolData plugin. Is there something special I need to do in order to make it stick?

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @BretBays
        last edited by ferdinand

        Hello @BretBays,

        Thank you for reaching out to us. It is impossible to give you a meaningful answer due to you not providing a complete problem description/code. Please have a look at Support Procedures: How to Ask Questions. A link box should retain its drag and drop inputs by default. Sounds a bit like you have a bug in your code which overwrites the value of the field.

        Cheers,
        Ferdinand

        Result

        Code

        import c4d
        import mxutils
        
        class LinkBoxDialog(c4d.gui.GeDialog):
            """Realizes a simple dialog with a link box.
            """
            ID_LINKBOX: int = 1000
        
            def CreateLayout(self) -> bool:
                """Creates the layout for the dialog.
                """
                self.SetTitle("LinkBoxDialog")
        
                self._linkBox: c4d.gui.LinkBoxGui = mxutils.CheckType(
                    self.AddCustomGui(
                        id=self.ID_LINKBOX,
                        pluginid=c4d.CUSTOMGUI_LINKBOX,
                        name="LinkBox",
                        flags=c4d.BFH_SCALEFIT,
                        minw=0,
                        minh=0,
                        customdata=c4d.BaseContainer()
                    ), c4d.gui.LinkBoxGui)
                
                return True
        
            def Command(self, cid: int, msg: any) -> bool:
                """Handles the command events for the dialog, here when something is dragged into the link 
                box.
                """
                if cid == self.ID_LINKBOX:
                    print("Link box new link: ", self._linkBox.GetLink())
                return True
        
        if __name__=='__main__':
            # Establish a global instance of the dialog so that we can have an async dialog. This will result
            # in a dangling dialog, please do not do this in production code. You always need a command data
            # plugin or another owning entity for an async dialog in production code.
            #
            # This is just for demonstration purposes so that we can drag and drop things into the link box.
            dlg: LinkBoxDialog = LinkBoxDialog()
            dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, default=100)
        

        MAXON SDK Specialist
        developers.maxon.net

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

          Does it still work if you instead put it into a SubDialog and call that subDialog from a tooldata plugin? Because thats the only thing I can see from my code. As ive mentioned, I have used linkboxes successfully before, but it was always in CommandData plugins with GeDialog not ToolData plugins with SubDialog so it shows up in the Attribute Manager vs a pop up window. I wasn't sure if there is some sort of difference I need to account for there.

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @BretBays
            last edited by

            Hey @BretBays,

            I cannot write code which demonstrates your problem for you. I already made here an exception and wrote code for you that demonstrates that it does work for me here. Please have a look at our Support Procedures, especially the How to Ask Questions and Examples section.

            Please provide executable code and a bug report when you think you have found a bug.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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

              I would do that, except the process for creating a plugin is not trivial and in no way intuitive anymore. There's no resEdit to generate all the necessary resource information and UI elements, so now I have to do that manually. That means to provide a meaningful and functional example, I need to now go through the several hoops of creating a plugin just for this test, despite giving reasonably clear indications of what is being done function and plugin-wise. And despite going through the process of getting a new plugin ID, and creating a new folder, .pyp file, res, strings_us, renaming my classes, using the correct, new, throwaway plugin ID, my plugin wont even initialize. Even though it's the same code as my actual plugin, just with renamed classes and file, and stripped down, the plugin would not initialize. So here's the code that had to hijack an existing toolData plugin SDK example to demonstrate the issue.

              """
              Copyright: MAXON Computer GmbH
              Author: XXX, Maxime Adam
              
              Description:
                  - Tool, Creates a liquid Painter Tool.
                  - Consists of Metaball and Sphere.
              
              Class/method highlighted:
                  - c4d.plugins.ToolData
                  - ToolData.GetState()
                  - ToolData.MouseInput()
                  - ToolData.Draw()
                  - ToolData.GetCursorInfo()
                  - ToolData.AllocSubDialog()
              """
              import c4d
              import os
              
              # Be sure to use a unique ID obtained from www.plugincafe.com
              PLUGIN_ID = 1025247
              
              # Values must match with the header file, usd by c4d.plugins.GeLoadString
              IDS_PRIMITIVETOOL = 50000
              
              
              class SettingsDialog(c4d.gui.SubDialog):
                  """Creates a Dialog to show the ToolData options.
              
                  This dialog will be displayed in the Attribute Manager so this means the ToolDemoDialog
                  will be instantiate each time the tool is activate and destruct when the AM change its mode.
                  """
                  def __init__(self, sharedDict):
                      super(SettingsDialog, self).__init__()
              
                  def CreateLayout(self):
                      self.SetTitle("BB-Distribute Points")
                      self.GroupBegin(id=100010, flags=c4d.BFH_SCALEFIT, cols=1, rows=5, title="BB-Distribute Points")
              
                      self.GroupBegin(id=200010, flags=c4d.BFH_SCALEFIT, cols=2, rows=1)
                      #bc=c4d.BaseContainer()
                      self.AddCheckbox(id=8888,  flags = c4d.BFH_LEFT,  initw=10, inith= 10, name="")
                      self.linkGadget = self.AddCustomGui(9898, c4d.CUSTOMGUI_LINKBOX, "Custom", c4d.BFH_SCALEFIT, minw=100, minh=10)
                      self.GroupEnd()
                      
                      self.GroupEnd()
              
                      return True
                  
                  def Command(self, commandID, msg):
                      """This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
              
                      It is also called when a string menu item is selected.
              
                      Args:
                          commandID (int): The ID of the gadget that triggered the event.
                          msg (c4d.BaseContainer): The original message container
              
                      Returns:
                          bool: False if there was an error, otherwise True.
                      """
                      if commandID == 9898:
                          print(self.linkGadget.GetLink())
              
              
              class LiquidTool(c4d.plugins.ToolData):
                  """Inherit from ToolData to create your own tool"""
              
                  def __init__(self):
                      self.data = {'sphere_size':15}
              
                  def AllocSubDialog(self, bc):
                      """Called by Cinema 4D To allocate the Tool Dialog Option.
              
                      Args:
                          bc (c4d.BaseContainer): Currently not used.
              
                      Returns:
                          The allocated sub dialog.
                      """
                      return SettingsDialog(getattr(self, "data", {'sphere_size': 15}))
              
              
              if __name__ == "__main__":
                  # Retrieves the icon path
                  directory, _ = os.path.split(__file__)
                  fn = os.path.join(directory, "res", "liquid.tif")
              
                  # Creates a BaseBitmap
                  bmp = c4d.bitmaps.BaseBitmap()
                  if bmp is None:
                      raise MemoryError("Failed to create a BaseBitmap.")
              
                  # Init the BaseBitmap with the icon
                  if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK:
                      raise MemoryError("Failed to initialize the BaseBitmap.")
              
                  # Registers the tool plugin
                  c4d.plugins.RegisterToolPlugin(id=PLUGIN_ID,
                                                 str="Py-Liquid PainterBB",
                                                 info=0, icon=bmp,
                                                 help="This string is shown in the statusbar",
                                                 dat=LiquidTool())
              
              

              This is a copy of an SDK example of a toolData plugin, it does the same thing I am doing in my plugin which is using and was as I described in my first post def AllocSubDialog(self, bc) in the toolData pluginto call a c4d.gui.SubDialog class that is calling CreateLayout(self) which inside there is using self.linkGadget = self.AddCustomGui(9898, c4d.CUSTOMGUI_LINKBOX, "Custom", c4d.BFH_SCALEFIT, minw=100, minh=10). I also added the Command bit from your example, and it will print the object, but the object does not get retained.

              Open Cinema. Select Py-Liquid PainterBB. Drag any object into the link field of the AM and it should disappear immediately.

              ferdinandF 1 Reply Last reply Reply Quote 0
              • ferdinandF
                ferdinand @BretBays
                last edited by

                Hey @BretBays,

                this very much looks like a bug. There is nothing you can do about this to fix this on your end. I have logged this as a normal (non-critical) bug under "ITEM#530442 [Python] CUSTOMGUI_LINKBOX not able to hold a link when used inside the SubDialog of a tool".

                Cheers,
                Ferdinand

                """
                We cannot even wiggle ourselves around the problem with manual drag handling, because SetLink and 
                CheckDropArea are fundamentally broken for this link box in a SubDialog, it seems to be dangling.
                """
                
                    ID_LINK_BOX: int = 1003
                
                    def CreateLayout(self):
                        """Called by Cinema 4D to populate the dialog.
                        """
                        self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT, cols=2, rows=1)
                        self.AddEditNumberArrows(id=1002, flags=c4d.BFH_MASK)
                        link: c4d.gui.LinkBoxGui = self.AddCustomGui(
                            self.ID_LINK_BOX, c4d.CUSTOMGUI_LINKBOX, "Custom", c4d.BFH_SCALEFIT, minw=100, minh=10)
                        if link is None:
                            raise MemoryError("Failed to create a LinkBoxGui.")
                        
                        # Here setting the link works.
                        link.SetLink(c4d.documents.GetActiveDocument().GetFirstObject())
                
                        self.GroupEnd()
                        return True
                    
                    def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int:
                        """Called by Cinema 4D to handle the dialog messages.
                        """
                        # This is never true, probably because the link box is dangling.
                        if msg.GetId() in (c4d.BFM_DRAGRECEIVE, c4d.BFM_DRAGEND):
                            if self.CheckDropArea(self.ID_LINK_BOX, msg, True, True):
                                print ("Hit")
                        
                        # How we could work around this, but this fails due to SetLink not working anymore in
                        # this context.
                        if msg.GetId() == c4d.BFM_DRAGEND:
                            # Unpack the drag data and check if the drag content is something we want to handle.
                            dragData: dict[str, any] = self.GetDragObject(msg)
                            dragType: int | None = dragData.get("type", None)
                            # An atom array, i.e., a list of C4DAtom objects has been dropped.
                            if dragType == c4d.DRAGTYPE_ATOMARRAY:
                                content: list[c4d.BaseList2D] = dragData.get("object", [])
                                if content and isinstance(content[0], c4d.BaseObject):
                                    print ("SetLink: ", content[0])
                                    link: c4d.gui.LinkBoxGui = self.FindCustomGui(self.ID_LINK_BOX, c4d.CUSTOMGUI_LINKBOX)
                                    # Here SetLink does not work anymore, it does not update the link box.
                                    link.SetLink(content[0])
                                    return True
                                
                        return c4d.gui.GeDialog.Message(self, msg, result)
                

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 0
                • ferdinandF ferdinand moved this topic from Cinema 4D SDK on
                • B
                  BretBays
                  last edited by

                  Blah! Bummer. Seemed like a bug to me. I guess I will just convert this to a CommandData plugin and use GeDialog.

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post