Plugin opens on Mac not correctly
-
-
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 # ...