ToolData Linkbox does not retain object when one is dragged in.
-
Howdy,
I found myself with some time to look at my plugin I was working on. It is a ToolData plugin that usesAllocSubDialog
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 functionself.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?
-
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,
FerdinandResult
-
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.
-
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 -
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 ac4d.gui.SubDialog
class that is callingCreateLayout(self)
which inside there is usingself.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.
-
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)
-
-
Blah! Bummer. Seemed like a bug to me. I guess I will just convert this to a CommandData plugin and use GeDialog.