Menu with checkmark



  • @anomalaus The DialogSimple methods are just function calls, they don't return the dialog as an object that can be inspected like some of the others. The generic Dialog method is the one that takes an XML format. It would be helpful if they included some kind of documentation, but no such luck. I tried using it and got something to popup after using the "Show" method (which they didn't bother to document). Looking again at one of the XML files the header says "wxWidgetCanvas". Maybe the format would make more sense if I knew how wxWidget Canvas worked, but not much luck on google. Maybe someone would find that info useful...



  • @adp Thanks for the reference code! It could be useful on another project I have planned, as using the basic AskMenu is getting to be a PITA on that one. I'd probably replace the checkboxes with a simpler multiselect list though. (BTW checkMARK and checkBOX are two entirely different things LOL) For the current applet, a simple dropdown list is all I need...even AskMenu's sparse implementation it's less than ideal.



  • @anomalaus There is a difference between Menu items and Disalog controls in wxPython.

    Maybe you want a Popup-Menu insteed of a Dialog?



  • @anomalaus said in Menu with checkmark:

    @semicharm yes. With @adp 's code, you can roll your own menu dialog. I'm guessing that the Poser devs probably didn't build into their DialogSimple.AskMenu() method any provisions for reading the format from an XML file, or bother with the niceties of parsing each menu item for escape characters to define check marks or display keyboard shortcuts, since there's another place to configure those, but it's incompatible with user created dynamic windows from Python.

    For me it looks like Poser-Devs simply included some "ready-to-go" standard dialogs from wxPython into Poser.

    It might just be possible to poke around in the exposed methods of the dialog returned from the AskMenu() call, but there's no documentation and no guarantee anything tried there would remain supported, even if it did work.

    Have a look into wxPythons folder in [poserdir]\Runtime\Python\Lib\site-packages\wx-2.9.1-msw\wx. There is a lot of comments in the code.

    Online documentation for wxPython is also available (attention: Poser is using version 2.9.1, current version is 4.1).
    Mac-Support for in 2.9 isn't up to date, even windows has some problems (I made an App with layers to mark UV-maps – works perfect with Linux but not with Windows, because wxPython 2.9 for Windows has remarkable problems with transparency).



  • @semicharm I'm going to make some more example-code with wxPython. Seems there is a need for this :)



  • Some things to note:

    In wxPython a "window" is simpy a rectangle area on screen.
    What we desktop-user know as "window" is a "Frame" in wxPython.
    But a frame is indeed only a frame: It handles things around "Panels" – like window-borders, window-caption, handles to make a window (frame) bigger or smaler, and a frame holds menus, tool- and statusbars.
    A "Panel" is where most things happen: Controls (like Buttons, Checkboxes) and even other Panels (if a Panel is used as a container) usualy have a Panel as parent.

    Poser is based on "Panes", not Frames. Panes can be seen als a specialized type of Frame, used for automatically layouted UI's (AUI-Manager). One should avoid using Frames with Poser. Poser may crash :)

    Another specialised "Frame" is a Dialog. Dialogs are usally forced to stay on top of all other Frames. Dialogs can be used without problems in windows (as long as their parent-window is a Pane managed by Poser).

    What does this all mean?

    With Poser-Python one has to use a Poser managed pane as the base of all user created "Windows":

        man = poser.WxAuiManager()
        root = man.GetManagedWindow()
        myPanel = wx.Panel(parent=root, ...)
    


  • Here is a commented piece of source for a listbox with actornames currently in use by Poser.
    You can move the window around and add it to the UI like any window owned by Poser (like Hirarchy-Editor etc.).
    0_1557592776285_Bildschirmfoto von 2019-05-11 18-35-01.png 0_1557592807060_Bildschirmfoto von 2019-05-11 18-37-18.png

    from __future__ import print_function
    
    try:
        import poser
    
        SCENE = poser.Scene()
    except poser.error:
        raise RuntimeError("Script must run in Poser.")
    
    import wx
    import wx.aui  # We need this to manage Poser's Panes.
    
    APP_NAME = "Poser Test Panel"
    APP_SIZE = (800, 600)
    BG_COLOR = 75, 75, 75
    FG_COLOR = 200, 200, 200
    
    
    ##############################################################################
    # The main window for our test.
    
    class AppPanel(wx.Panel):
        def __init__(self, parent,
                     name=APP_NAME,
                     actorlist=list(),
                     size=APP_SIZE,
                     style=wx.DEFAULT | wx.NO_BORDER | wx.FRAME_TOOL_WINDOW,
                     ):
            super(AppPanel, self).__init__(parent=parent,
                                           id=wx.NewId(),
                                           name=name,
                                           style=style,
                                           size=size)
    
            self.SetBackgroundColour(BG_COLOR)
            self.SetForegroundColour(FG_COLOR)
    
            # A sizer to handle our control, vertically arranged.
            # The sizer is useful even for one single item because
            # it handles scrollbars for us.
            sizer = wx.BoxSizer(wx.VERTICAL)
    
            # Create "choices" for left listbox
            choices = [ac.Name() for ac in actorlist]
    
            # Listbox style: Extended multiple selection and
            # vertical scrollbars only when needed.
            lb_style = wx.LB_EXTENDED | \
                       wx.LB_MULTIPLE | \
                       wx.LB_NEEDED_SB
    
            self.listbox = wx.ListBox(parent=self, id=-1,
                                      choices=choices,
                                      style=lb_style)
    
            # After creating the listbox we place it into the sizer.
            sizer.Add(self.listbox, 1, wx.ALL | wx.EXPAND)  # expand control as far as possible.
            sizer.Layout()  # Let wxPython arange and resize the control.
    
            # Add the sizer to this panel.
            self.SetSizerAndFit(sizer)
    
    
    ##############################################################################
    
    aui = poser.WxAuiManager()
    root = aui.GetManagedWindow()
    
    # Create our window
    OurMainPanel = AppPanel(parent=root,
                            name=APP_NAME,
                            actorlist=SCENE.Actors())
    
    # Now get a pane (a window/frame) from Poser's AUI-Manager
    pane = wx.aui.AuiPaneInfo()
    
    # We can customize the pane a bit:
    pane.Resizable().Float().FloatingSize(wx.Size(200, 400)).BestSize(wx.Size(100, 200))
    # As you can see, each call returns the pane so we can build chains.
    # The convertinal way is:
    pane.PaneBorder(True)
    pane.Movable(False)
    pane.CaptionVisible(True)
    pane.DestroyOnClose(True)
    pane.PinButton(True)
    # Could also be written as: pane.PaneBorder(True).Movable(False)....
    
    # After this is done we just have to add our Panel to the pane we got from AUI-manager
    # and give up management to Poser-wxPython.
    aui.AddPane(OurMainPanel, pane)
    pane.Show()
    aui.Update()
    
    


  • There is a small error in the code above. The check if Poser is available is wrong. It shuld look like:

    try:
        import poser
        SCENE = poser.Scene()
    except ImportError:
        raise RuntimeError("Script must run in Poser.")
    

    (insteed of checking poser.error)



  • The second demo code is using events to react on mouseclicks (selections):

    from __future__ import print_function
    
    try:
        import poser
    
        SCENE = poser.Scene()
    except ImportError:
        raise RuntimeError("Script must run in Poser.")
    
    import wx
    import wx.aui  # We need this to manage Poser's Panes.
    import wx.dataview
    
    APP_NAME = "Poser Test Panel"
    APP_SIZE = (800, 600)
    BG_COLOR = 75, 75, 75
    FG_COLOR = 200, 200, 200
    
    
    ##############################################################################
    # The main window for our test.
    
    class AppPanel2(wx.Panel):
        def __init__(self, parent,
                     name=APP_NAME,
                     actorlist=list(),
                     size=APP_SIZE,
                     style=wx.DEFAULT | wx.NO_BORDER | wx.FRAME_TOOL_WINDOW,
                     ):
            super(AppPanel2, self).__init__(parent=parent,
                                           id=wx.NewId(),
                                           name=name,
                                           style=style,
                                           size=size)
    
            self.SetBackgroundColour(BG_COLOR)
            self.SetForegroundColour(FG_COLOR)
    
            # A sizer to handle our control, vertically arranged.
            # The sizer is useful even for one single item because
            # it handles scrollbars for us.
            sizer = wx.BoxSizer(wx.VERTICAL)
    
            # Listbox style is single selection (standard)
            # because we want to be informed for any single
            # selection that happens.
            lb_style = wx.LB_NEEDED_SB
    
            # Because we want to fill the box with objects (see below),
            # we leave "coices" empty.
            self.listbox = wx.ListBox(parent=self, id=-1,
                                      choices=[],
                                      size=wx.DefaultSize,
                                      style=lb_style)
            self.listbox.SetBackgroundColour(BG_COLOR)
            self.listbox.SetForegroundColour(FG_COLOR)
    
            # Prepare the listbox to hold some useful data.
            for ac in actorlist:
                entry = dict(name=ac.Name(),
                             iname=ac.InternalName(),
                             onoff=ac.OnOff())
                self.listbox.Append(entry.get("name"), entry)
    
            # To do something useful with the listbox, we need to
            # know if the user clicks on an entry.
            self.listbox.Bind(wx.EVT_LISTBOX, self.onSelection)
    
            # After creating the listbox we place it into the sizer.
            sizer.Add(self.listbox, 1, wx.ALL | wx.EXPAND)  # expand control as far as possible.
    
            # Create a close-button:
            self.closebtn = wx.Button(self, -1, 'Close')
            # Trigger event if button is pressed:
            self.closebtn.Bind(wx.EVT_LEFT_UP, self.onClose)
            # Add button to sizer:
            sizer.Add(self.closebtn, 0, wx.ALIGN_CENTER_HORIZONTAL)
    
            sizer.Layout()  # Let wxPython arange and resize the control.
    
            # Add the sizer to this panel.
            self.SetSizerAndFit(sizer)
    
            # Another useful event is when the window is closed by the user.
            self.Bind(wx.EVT_CLOSE, self.onClose)
            self.Bind(wx.EVT_WINDOW_DESTROY, self.onDestroy)
    
        def onSelection(self, event):
            """
            This function is called if an item is selected/deselcted.
            Parameter <event> has more information (see wxPython documention
            about events).
            """
            selection = self.listbox.GetStringSelection()
            print("onSelection:", selection)
            if selection and selection.lower() != "universe":
                # Get the object we saved in this list item.
                # GetSelection() returns <int>index-position.
                obj = self.listbox.GetClientData(self.listbox.GetSelection())
                # Remember: the object is a dictionary.
                iname = obj.get("iname")
                onoff = obj.get("onoff")
                # to show that anything works, we select this actor
                actor = SCENE.ActorByInternalName(iname)
                SCENE.SelectActor(actor)
                SCENE.DrawAll()
    
        def onClose(self, event):
            print("Closebutton pressed:", self.listbox.GetSelections())
            self.Parent.Close()
    
        def onDestroy(self, event):
            # Called if this window is destroyed
            print("On Destroy:", self.listbox.GetSelections())
            event.Skip()
    
    ##############################################################################
    
    aui = poser.WxAuiManager()
    root = aui.GetManagedWindow()
    
    # Create our window
    OurMainPanel = AppPanel2(parent=root,
                            name=APP_NAME,
                            actorlist=SCENE.Actors())
    
    # Now get a pane (a window/frame) from Poser's AUI-Manager
    pane = wx.aui.AuiPaneInfo()
    
    # We can customize the pane a bit:
    pane.Resizable().Float().FloatingSize(wx.Size(200, 400)).BestSize(wx.Size(100, 200))
    # As you can see, each call returns the pane so we can build chains.
    # The convertinal way is:
    pane.PaneBorder(True)
    pane.Movable(False)
    pane.CaptionVisible(True)
    pane.DestroyOnClose(True)
    pane.PinButton(True)
    # Could also be written as: pane.PaneBorder(True).Movable(False)....
    
    # After this is done we just have to add our Panel to the pane we got from AUI-manager
    # and give up management to Poser-wxPython.
    aui.AddPane(OurMainPanel, pane)
    pane.Show()
    aui.Update()
    
    


  • Thanks @adp, this is great stuff.

    It's very logical that one will have complete control over, and access to all features of an interface that one has created directly oneself, but are the controls and widgets of any of the built-in Poser panes accessible?

    I have been struggling with the RayTrace Preview panel, which I can get by name from the aui manager, but the only panes I can access meaningfully are the primary one I need (the actual RayTrace, which I call an OS routine to save to a PNG file) and the miniframe which holds the OS close/minimise/expand buttons. What I really need to be able to interrogate and manipulate are the Auto-update check box, the Refresh button, and the progress indicator, so I can initiate a preview raytrace, detect when it has finished and then save it in one operation, rather than relying on the user to Refresh and then run a script to save.

    I've been able to determine how to find out whether the pane is visible:

    RTPName = 'RayTracePreview'
    # Get the RayTrace Preview window's content bounding box
    bbox = None
    aui = poser.WxAuiManager()
    rtpPane = aui.GetPane( RTPName )
    # Ought to check whether pane is visible before committing to save it's content
    if not rtpPane.IsShown():
    	rtpPane.Show()
    	aui.Update()
    xpos, ypos, width, height = rtpPane.window.ClientRect
    left = xpos + ( width - RTPWidth ) / 2 # Image centred in pane
    top = ypos + ( height - RTPHeight ) / 2 # Image centred in pane
    left, top = rtpPane.window.ClientToScreen( ( left, top ) )
    right = left + RTPWidth
    bottom = top + RTPHeight
    if debug:
    	print xpos, ypos, width, height
    	print left, top, right, bottom
    bbox = ( left, top, right, bottom )
    

    Any clues, or is this actually impossible?



  • @anomalaus said in Menu with checkmark:

    It's very logical that one will have complete control over, and access to all features of an interface that one has created directly oneself, but are the controls and widgets of any of the built-in Poser panes accessible?

    Theoretically: yes. In practice it can be a bit difficult to realize.

    What I really need to be able to interrogate and manipulate are the Auto-update check box, the Refresh button, and the progress indicator, so I can initiate a preview raytrace, detect when it has finished and then save it in one operation, rather than relying on the user to Refresh and then run a script to save.

    It is not sure if the Poser-GUI is done with Python or C. If it is written in C, you can't reach Python-like objects. On the other side, WX (and wxPython) is based on events.

    To find out more, you would have to bend the event handler of the relevant Panes to your own functions.
    Problem is, the eventhandler and other relevant functions to handle a control can reside in a totally different pane/window (control is in Pane A, but is controlled by functions in Pane B).

    May I ask what you want to archive?



  • @adp I want to initiate a RayTrace Preview, wait till it has completed, and then call an OS routine to screen capture it and save it as a PNG file in a single action. I can currently save a completed RT Preview, but have to manually click to Refresh and wait till it finishes.

    The entire scenario is necessary because QueueManager Renders cannot replicate Live or background renders because the save-to-file process for the queue corrupts the file, leaving pokethrough artifacts, because it re-orders inherited deformer evaluation orders during scene save and load. This is a bug which has been around since Poser Pro 2014, at least, but no one notices because they don't push the envelope quite as hard in the particular directions I do ;-) It also took me years to figure out that it wasn't my fault that fitting poses didn't work correctly when applied to re-opened, old scene files.



  • @anomalaus Maybe you can solve your problem while generating click-events. Not the most elegant, but...

    Here is a piece of code to generate a click-event in a known window:

            def click_test():
                window = app.frame.imgPanel.ImgWindow # get the window of interest
                ev = wx.MouseEvent() # create an Event Object...
                ev.SetPosition(wx.Point(100, 100)) 
                ev.SetEventType(wx.EVT_LEFT_DOWN.typeId)
                ev.SetEventObject(window) # Where the generated event is generated..
                window.ProcessEvent(ev) # Send event to the window.
    
    


  • I didn't try it, but it may also be possible to generate a button-click-event directly in the window/pane.