Plugin opens on Mac not correctly
-
I have a plugin on Mac and PC.
On PC it opens fine (fully), but on the Mac only the top is shown.
The code is the same on PC and Mac.
I realize this is a very open question, but could you give me some hints where to look?
Here some code, that might be related?class MenuCommand(c4d.plugins.CommandData): dialog = None area = Area() areaRender = AreaRender() def Execute(self, doc): if self.dialog is None: self.dialog = TreeViewDialog(self.area, self.areaRender) return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID_TGSTEXTUREMANAGER) #, defaulth=600, defaultw=600) Did not work! def RestoreLayout(self, sec_ref): if self.dialog is None: self.dialog = TreeViewDialog(self.area, self.areaRender) return self.dialog.Restore(PLUGIN_ID_TGSTEXTUREMANAGER, secret=sec_ref) ... okyn = c4d.plugins.RegisterCommandPlugin(PLUGIN_ID_TGSTEXTUREMANAGER, PLUGINSTRING, 0, bmp, PLUGINSTRING, MenuCommand())
-
Hello @pim,
Thank you for reaching out to us. I cannot test it right now, because my mac is at home, and I am at work - genius planning, I know . I will give it a spin tomorrow, but three things stand out for me which you might be able to answer before I pick up your topic tomorrow.
- Judging from your code, your core claim seems to be here that
defaulth
anddefaultw
have no effect forGeDialog.Open
on macOS, at least when the dialog mode isDLG_TYPE_ASYNC
? - Even when you pass no default values, a dialog should automatically resize itself to its minimum boundaries when the layout has been done correctly (using
BFH_SCALEFIT
andBFV_SCALEFIT
on the relevant groups and at least outer group). You unfortunately do not share your dialog code. I would urge you to do so, as helping you will otherwise be hard. As always, when you do not want to share code publicly, you can do so confidentially viasdk_support(at)maxon(dot)net
. - The title of your dialog says 'Texture Manager 2.0', which makes it sound like this is a regression. Is that true, did the plugin of yours work in prior versions of Cinema 4D macOS and is only faulting with 2023.x.x ? If so, in which version did it work, and in which revision of 2023 is it failing for you?
Cheers,
Ferdinand - Judging from your code, your core claim seems to be here that
-
Hi Ferdinand,
Thanks for the quick reply.
- yes the dialog is DLG_TYPE_ASYNC and defaulth and defaultw have no effect.
- I did not put in the complete dialog, because there is a lot of code.
After Adding a menu, here the code showing we use BFH_SCALEFIT|c4d.BFV_SCALEFIT for the top Group.
def CreateLayout(self): ... add menu # 3 koloms dialog self.GroupBegin(ID_GRP_SUB, flags=c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT, cols=3, groupflags =c4d.BFV_GRIDGROUP_ALLOW_WEIGHTS) self.GroupBorderSpace(10, 0, 0, 10) # left, top, right, bottom)
- No, it was there already in previous versions, yet I am nor sure when it started (which version).
And yes, I fully understand it is hard to answer such a generic question.
I was hoping you had seen this issue before.
I will try to delete a lot of code, to find where this issue is coming from.Note: after resizing the plugin dialog, the next time you open it in the same cinema 4d session, it is ok.
Pim
-
-
Update.
Using "BFH_SCALEFIT|c4d.BFV_SCALEFIT for the top Group" did scale the plugin dialog, but it used the given values (600x600).
So, still the whole dialog was not shown.I will keep on testing.
Regards,
Pim -
Hello @pim,
So, I gave it a spin but I cannot reproduce it, tested with 2023.0 and 2023.1. Which was not too surprising, because a bug on such a high level seemed unlikely. That does not mean that there is not any bug, but without your dialog code we cannot help you. Please also make sure to strip your code from irrelevant things and that is is executable.
What you should check:
- Did you make the dialog part of a layout where you scaled the dialog down? When you then load that layout, the dialog will always open in the scaled down state.
- There are indicators that you implement a
GeUserArea
which you use in the dialog. Make sure that it returns its correct minimum height and width. - If that does not help, you should debug your
CreateLayout
, this looks very much like a layout mistake one can easily make by marking something as not vertically fitting, causing the dialog to minimize its vertical space unless it is forced to be taller.
Find below the code I used.
Cheers,
FerdinandOpening the dialog without a width or height value or with it. These values do not have any impact because the outmost layout group is set to scale and fit both vertically and horizontally, i.e., the dialog will match the size of its content. The odd border spacings are cause by the values you set in your code.
"""MacOS GeDialog.Open Test """ import c4d class SimpleDialog (c4d.gui.GeDialog): """Example dialog that does nothing. """ ID_GRP_MAIN: int = 1000 def CreateLayout(self) -> bool: """Called by Cinema 4D to populate the dialog with gadgets. """ self.SetTitle("SimpleDialog") self.GroupBegin( SimpleDialog.ID_GRP_MAIN, flags=c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT, cols=3, groupflags =c4d.BFV_GRIDGROUP_ALLOW_WEIGHTS) self.GroupBorderSpace(10, 0, 0, 10) i: int = 2000 for _ in range(10): i += 1 self.AddStaticText(i, c4d.BFH_SCALEFIT, 0, 0, f"Item {i}:") i += 1 self.AddEditText(i, c4d.BFH_SCALEFIT) i += 1 self.AddCheckbox(i, c4d.BFH_FIT, 0, 0, "Foo") self.GroupEnd() return True class MenuCommand(c4d.plugins.CommandData): DIALOG: SimpleDialog = None PLUGIN_ID: int = 1060499 def Execute(self, doc): if MenuCommand.DIALOG is None: MenuCommand.DIALOG = SimpleDialog() return MenuCommand.DIALOG.Open(c4d.DLG_TYPE_ASYNC, MenuCommand.PLUGIN_ID) #, defaulth=600, defaultw=600) def RestoreLayout(self, sec_ref): if MenuCommand.DIALOG is None: MenuCommand.DIALOG = SimpleDialog() return MenuCommand.DIALOG.Restore(MenuCommand.PLUGIN_ID, secret=sec_ref) def RegisterPlugins() -> bool: """Registers the example. """ return c4d.plugins.RegisterCommandPlugin( id=MenuCommand.PLUGIN_ID, str="MenuCommand", info=c4d.PLUGINFLAG_SMALLNODE, icon=None, help="MenuCommand", dat=MenuCommand()) if __name__ == '__main__': if not RegisterPlugins(): raise RuntimeError( f"Failed to register {MenuCommand} plugin.")
-
Thanks.
I am stripping the plugin more and more, to see where the error / issue occurs. -
PS: I added a point, this might be related to the user area your code is hinting at to be used in the dialog.
-
Yes, I am using 2 user areas.
I will check. -
Oops, my fault!
After stripping a lot of the plugin, I came to the conclusion that
GroupBegin() and GroupEnd() where not matching.This is apparently more importan on the Mac than on the PC!
Thanks for the patience and the support.
Regards,
Pim -
No worries, I am glad that you found your fix!
-
Alas, I was wrong!
After trying to match GroupBegin() and GroupEnd(), it proved to be ok.
So, back to stripping the code to see where it fails.Doing that I found out another (related) issue.
After stripping to only using a Treeview, I noticed that the dialog also did open completely. So yes related to the initial issue (I think).It should open like this (I resized the dialog window myself).
After checking the example "C4D TreeView example", I am beginning to belive the issue is not in the dialog code, but in the filling of the tree.
If you could have a look, I will send the code and some data to sdk_support(at)maxon(dot)net.
-
Hello @pim,
In addition to my answer via mail, I will also answer here, as this might be interesting for the rest of the community.
Cheers,
Ferdinand
So, the question was here "Why does my tree view not open with the right size?". The easy answer to this is that:
- You did neither set a minimum size for the dialog in GeDialog.Open().
- Nor one for the tree view itself via GeDialog.AddCustomGui().
Both in conjunction did result in your dialog collapsing down to zero height.
What can I do?
Not much, the TreeViewCustomGui is not designed to scale to the size of its content. The underlying question is what you expect to happen here.
a. Just have the tree view have some fixed minimum size, regardless of its content.
b. Have the tree view initialize automatically to size, i.e., when the view has 10 items upon opening, it should have exactly 10 items height.When it is (a.) what you want, then this is easily doable with the minimum size passed to GeDialog.AddCustomGui(). When it is (b.), then you are more or less out of luck, as a tree view cannot scale automatically to the size of its content.
You can adjust the minimum size dynamically based on the content which is going to be placed in the tree view, but when the content changes, you will have to flush your layout in order to be able to set a new minimum size.
On a practical level it is also not so desirable to have a tree view scale like this, as this minimum height is not well defined. Should it be all items, or just all top level items, i.e., fully collapsed or fully expanded (which is implied by your example as all nodes start out as expanded). Let's say we choose fully collapsed. What happens when you have so many root nodes that the tree view will not fit on screen when making space for all root nodes.
Example
Result
The dialog is set to have a minimum height which matches the total number of nodes in it.
Code
I had to cut here a bit, but the relevant parts are:
class TreeNode: # ... def __len__(self): """(f_hoppe): Counts all descendants of this node, including the node itself. Implemented fully recursively. Should be implemented iteratively for production due to stack overflows and Python's recursion limit preventing them. Or the data should be acquired when textures are collected. """ count: int = 1 for child in self.children: count += len(child) class ListView(c4d.gui.TreeViewFunctions): COLUMN_COUNT: int = 1 MIN_LINE_HEIGHT: int = 24 MIN_WIDTH: int = 500 def __init__(self): # The root nodes of the tree view. self._rootNodes: list[TreeNode] = [] def __len__(self): """(f_hoppe): Returns the number of tree nodes in the instance. """ return sum([len(node) for node in self._rootNodes]) def GetMinSize(self) -> tuple[int, int]: """(f_hoppe): Returns the minimum GUI size for the data of this ListView instance. """ # But all these classic API pixel values are quite wonky anyways and the tree view does # many custom things. So we must do some ugly magic number pushing. Subtracting nine units # from the actual height of each row gave me sort of the best results, but the tree view # GUI does not scale linearly in height with the number of rows. Meaning that what looks # good for 5 items might not look good for 50 items. return (ListView.MIN_WIDTH, (ListView.MIN_LINE_HEIGHT - 9) * len(self)) def GetColumnWidth(self, root: TreeNode, userdata: None, obj: TreeNode, col: int, area: c4d.gui.GeUserArea): """(f_hoppe): This cannot be a constant value, as we will otherwise clip data. """ return area.DrawGetTextWidth(obj.textureName) + 24 def GetLineHeight(self, root, userdata, obj, col, area): """(f_hoppe): Used constant value. """ return ListView.MIN_LINE_HEIGHT # ... class SimpleDialog (c4d.gui.GeDialog): ID_TRV_TEXTURES: int = 1000 def __init__(self) -> None: self._treeView: c4d.gui.TreeViewCustomGui = None # This builds the tree node data so that self._listView._rootNodes holds the top level # node(s) for the tree managed by this ListView instance. self._listView: ListView = self.GetTree() super().__init__() def CreateLayout(self) -> bool: """(f_hoppe): I substantially rewrote this. """ # Because we already initialized the ListView data, we can use it to compute the minimum # size of the gadget. w, h = self._listView.GetMinSize() print(f"{self._listView.GetMinSize() = }, {len(self._listView) = }") # Use these values to define a default size so that it shows all columns. What you want to # be done here is sort of not intended by the TreView GUI, although admittedly desirable. # The closest thing we can do is set the minimum size for the gadget, so that all lines # will fit into it. This will then ofc also have the side effect that the GUI cannot be # scaled down beyond this point. You might want to implement ListView.GetMinSize() in a # different manner, so that it does not take into account all nodes and instead just the # top level nodes. self._treeView = self.AddCustomGui( id=SimpleDialog.ID_TRV_TEXTURES, pluginid=c4d.CUSTOMGUI_TREEVIEW, name="", flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, minw=w, minh=h, customdata=SimpleDialog.SETTINGS_TREEVIEW) if not isinstance(self._treeView, c4d.gui.TreeViewCustomGui): raise MemoryError(f"Could not allocate tree view.") # If you want to do this at runtime, i.e., load a new texture path, you would have to # call GeDialog.LayoutChanged() on the layout group which contains the tree view, add # the tree view again (with new min size values), and then call GeDialog.LayoutChanged() # on the group. It might be easier to just live with a fixed minimum size just as # (300, 300) return True # ...