Results 1 to 7 of 7

Thread: Some how to's

  1. #1
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127

    Some how to's

    Hi together,

    I want to start a thread, in which we, the users, can spread things that we found out. I hope this will reduce the "search time" in the docs of the python lwsdk. I want to encourage all interested users to write things they found out.

    Yes many things tht I will Show in the example can be made better or eleganter (for shure). But this can be used as a hint on how this can be used. I am shure that not only python experts want to write python scripts. And I found it hard to find Information related to "how to use" the lwsdk, even for the simplest things I searched realy hard.

    The attached zip should be installed as follows:
    The one and only folder in the zip should be moved to any Folder in which python scripts are searched for. On my machine I used:

    LWInstall\Support\plugins\scripts\Python
    In this Folder, there is another Folder (which will hold a library) and the nuArches.py which is the runable script.

    I hope this will aid some users in doing the first steps with python. And I hope also, that this thread will be extendet by others with useful hints.

    nuArchitectPy.zip

    Regards,
    KANUSO

  2. #2
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127
    I noticed, that the whole script of nuArches.py is also no direct help. So I start here with a Brief description of what I found out with multi column listboxes.

    Code:
    # the list of text-lists in the listbox
    lbtxt=[["col 1 of row 1","col 2 of row 1",...,"col n of row 1"],
           ["col 1 of row 2","col 2 of row 2",...,"col n of row 2"],
           ["col 1 of row 3","col 2 of row 3",...,"col n of row 3"],
           [ ... ],
           ["col 1 of row n","col 2 of row n",...,"col n of row n"]]
    
    mlblast=0       # remember the last selection
    
    # the select callback of the listbox
    def lbselect(self,ctl,udat,row,sel):
        global mlblast               # make shure we write to the global variable
        if row < 0:                  # not interested if no row is clicked
            return
        self.lbox.set_int(-1)        # ctl can also be used: ctl.set_int(-1) - -1 = deselect active selection
        mlblast=row                  # remember last selection
        self.lbox.set_int(mlblast)   # ctl can also be used: ctl.set_int(mblast)
        txt1=lbtxt[row][0]           # the text of the columns of the actual row
        txt2=lbtxt[row][1]
        txt3=lbtxt[row][2]
        ...
        txtn=lbtxt[row][n-1]
    
    # the name user_func_callback of the listbox
    def lbname(self,ctl,udat,row,col):
        mltitles=["Dir","SCX","SCY","MOX","MOY","UX","UY","Cor"]    # the wanted titles of the columns
        if row<0:                    # the titles of the columns are requested
            return(mltitles[col])    # return the title of the requested column
        return lbtxt[row][col]       # return the content of column 'col' of row 'row'
    
    # the count user_func_callback of the listbox
    def lbcount(self,ctl,udat):
        return len(lbtxt)            # return the rowcount of the listbox
    
    # the colwidth user_func_callback of the listbox
    def lbcolwidth(self,ctl,udat,col):
        if col==0: return(25)        # return the width of the requested column 'col'
        if col<5: return(60)
        if col<7: return(25)
        if col<8: return(45)
        return(0)                    # must return 0 if no more column is left because this callback will be called until it returns 0
                                         # this is how the framework recognizes the count of columns
    
    
    # to change the text of an individual cell
    lbtext[row][column]="example"
    
    # to get the text of an individual cell
    example=lbtext[row][column]
    
    # to get the actual selected row
    selectionindex=self.lbox.get_int()
    
    # to set the actual selected row
    self.lbox.set_int(selectionindex)
    
    # to create the listbox all dimensions and positions are pixel coordinates
    self.lbox=self.panel.multilist_ctl(title,width,height,lbname,lbcount,lbcolwith) # I didn't figure out for what title can be used
    self.lbox.set_w(width)                # set the width of the listbox (the width and height is already set)
    self.lbox.set_h(height)               # set the height of the listbox (the width and height is already set)
    self.lbox.move(xpos,ypos)             # set the position of the listbox
    self.lbox.set_select(self.lbselect)   # set the select callback of the listbox
    self.lbox.set_int(mlblast)            # set the selected row
    I hope this will aid someone.

    The code above is not really runable, because of the "..." Statements, but I hope it will help anyway.

    Regards,
    KANUSO
    Last edited by KANUSO; 04-11-2018 at 02:01 AM.

  3. #3
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127
    Here the how-to related on tabchoice_ctl:

    It will Show you how you can Switch between Tabs without loosing the values in the variables (maybe this can be done eleganter, but it works)

    Code:
    tabs=['Tab1','Tab2','Tab3']  # titles of the tabs
    lasttab=0       # last tab clicked
    reqw=600        # the width of the requester
    reqh=450        # the height of the requester
    
    # check if x,y is in the rectangle build of x1,y1 - x2,y2
    def isinbox(x,y,x1,y1,x2,y2):
        if x>=x1:
            if x<=x2:
                if y>=y1:
                    if y<=y2:
                        return(1)    # yes, return 1
        return(0)    # no, return 0
    
    # sub to get the vals from the controls on tab 1
    def gettab1(self):
        self.txtctltab1val=self.txtctltab1.get_str()    # string control
        self.intctltab1val=self.intctltab1.get_int()    # integer control
        self.fltctltab1val=self.fltctltab1.get_float()  # float control
    
    # sub to get the vals from the controls on tab 2
    def gettab2(self):
        self.txtctltab2val=self.txtctltab2.get_str()    # string control
        self.intctltab2val=self.intctltab2.get_int()    # integer control
        self.fltctltab2val=self.fltctltab2.get_float()  # float control
    
    # sub to get the vals from the controls on tab 3
    def gettab3(self):
        self.txtctltab3val=self.txtctltab3.get_str()    # string control
        self.intctltab3val=self.intctltab3.get_int()    # integer control
        self.fltctltab3val=self.fltctltab3.get_float()  # float control
    
    # sub to get the vals from all tabs
    def getalltabs(self):
        gettab1()    # get vals from tab 1
        gettab2()    # get vals from tab 2
        gettab3()    # get vals from tab 3
    
    # on switching to an other tab, the controls of the actual tab must been erased
    def untab(self):
        global lasttab              # make shure we write to the global variable
        if lasttab==0:              # this means tab 1 was last visible
            self.gettab1()          # at first grab the variables from tab 1
            self.txtctltab1.erase() # erase txtctl
            self.intctltab1.erase() # erase intctl
            self.fltctltab1.erase() # erase fltctl
        elsif lasttab==1:           # this means tab 2 was last visible
            self.gettab2()          # at first grab the variables from tab 2
            self.txtctltab2.erase() # erase txtctl
            self.intctltab2.erase() # erase intctl
            self.fltctltab2.erase() # erase fltctl
        elsif lasttab==2:           # this means tab 3 was last visible
            self.gettab3()          # at first grab the variables from tab 3
            self.txtctltab3.erase() # erase txtctl
            self.intctltab3.erase() # erase intctl
            self.fltctltab3.erase() # erase fltctl
        lasttab=self.tabctl.get_int()    # get the now visible tab and remember it
    
    # activate the controls on tab 1
    def tab1(self):
        self.txtctltab1=self.panel.str_ctl("String",1)     # create a string control
        self.intctltab1=self.panel.int_ctl("Integer",1)    # create a integer control
        self.fltctltab1=self.panel.float_ctl("Float",1)    # create a float control
    
    # activate the controls on tab 2
    def tab2(self):
        self.txtctltab2=self.panel.str_ctl("String",1)     # create a string control
        self.intctltab2=self.panel.int_ctl("Integer",1)    # create a integer control
        self.fltctltab2=self.panel.float_ctl("Float",1)    # create a float control
    
    # activate the controls on tab 3
    def tab3(self):
        self.txtctltab3=self.panel.str_ctl("String",1)     # create a string control
        self.intctltab3=self.panel.int_ctl("Integer",1)    # create a integer control
        self.fltctltab3=self.panel.float_ctl("Float",1)    # create a float control
    
    # the select callback for the tab
    def select_event_func(self, control, user_data):
        i = self.tabctl.get_int()   # get integer associated with selected tab (index of the selected tab)
        self.untab()                # remove all controls related to the actual tab
        # build the controls related to the newly selected tab
        if i == 0:
            self.tab1()     # tab 1
        elif i == 1:
            self.tab2()     # tab 2
        elif i == 2:
            self.tab3()     # tab 3
        # it seems like the requester gets resized by the PCore funcs sometimes
        # I did find nothing on how to avoid this, so I do a resize, if the size isn't anymore the one I want
        if self.panel.getw()!=reqw:    # requesterwidth is not equal the width I want
            self.panel.setw(reqw)      # set it again
        if self.panel.geth()!=reqh:    # requesterheight is not equal the height I want
            self.panel.seth(reqh)      # set it again
    
    # handler for mousemove
    # here we have to react on "hoover" over our custom buttons
    # be careful, this will be called on every mousemove
    # do not draw too many things, to avoid flickering
    def mousemove(self,dontknow1,dontknow2,btnflag,x,y):
        if self.isinbox(x,y,x1,y1,x2,y2)==1:   # x,y is in the rectangle build of x1,y1 - x2,y2
            print "we are in the rectangle"
    
    # handler for mousebuttons
    def mousebtn(self,dontknow1,dontknow2,btnflag,x,y):
        if btnflag==32:     # only react one time (this is called for every change on the flag)
            if self.isinbox(x,y,x1,y1,x2,y2)==1:
                print "we are in the rectangle"
    
    # handler for the mousewheel
    def mousewheel(self,dontknow1,dontknow2,direction,x,y):
            if direction==1:
                wheeldir="out"
            else:
                wheeldir="in"
            print "mousewheel ",wheeldir," at X:",x," Y:",y
    
    
    
    # the init of all is in the process callback of the script (entrypoint of the application)
    def process(self,mod_command):
        global lasttab                      # make shure we write into the global variable named lasttab
        ...
        self.ui=lwsdk.LWPanels()            # get shortcut to the ui
        self.panel=self.ui.create("Title")  # create the panel
        self.panel.setw(reqw)               # set the width of the panel (can be changed by the framework)
        self.panel.seth(reqh)               # set the height of the panel (can be changed by the framework)
        ...
        # here you can set the callbacks for some actions. The flag corresponding to the action must be set
        # at the creation of the requester
        # here some callbacks of interest
        # self.panel.set_resize_callback(self.panelresize)  # the callback is called on resize of the tab
                                                            # I have not tested this callback until now
        self.panel.set_mouse_move_callback(self.mousemove)  # the callback is called on mouse movement
        self.panel.set_mouse_button_callback(self.mousebtn) # the callback is called on clicking with the mouse
        self.panel.set_mouse_wheel_callback(self.mousewheel)# the callback is called on mouse wheel action
        ...
        self.tabctl=self.panel.tabchoice_ctl("Title",tabs)  # The string "Title" would be normally empty
                                                            # tabs is a list ob tabnames (see above)
        self.tabctl.set_event(self.select_event_func)       # set the callback for tab selection
        # bring all tabs (the controls in them) to a defined state
        self.tab1()    # create tab 1
        lasttab=0      # set the active tab variable to reflect tab 1 is selected
        self.untab()   # grab the variables of the controls and erase the controls
    
        self.tab2()    # create tab 2
        lasttab=1      # set the active tab variable to reflect tab 2 is selected
        self.untab()   # grab the variables of the controls and erase the controls
    
        self.tab3()    # create tab 3
        lasttab=2      # set the active tab variable to reflect tab 3 is selected
        self.untab()   # grab the variables of the controls and erase the controls
    
        self.panel.setw(reqw)   # resize again (the framework can have changed the dimensions of the requester)
        self.panel.seth(reqh)   # resize again
        self.tab1()    # now show tab 1
        lasttab=0      # set the active tab variable to reflect tab 1 is selected
    
        # Displays the panel
        # create a modal window
        # do not use standard buttons
        # call the mouse-handlers
        # and call also on mousemove
        # the last flag has no function at the moment.
        if self.panel.open(lwsdk.PANF_BLOCKING | lwsdk.PANF_NOBUTT | lwsdk.PANF_MOUSETRAP | lwsdk.PANF_MOUSETRACK | lwsdk.PANF_FRAME) == 0:
            self.ui.destroy(self.panel)
            return lwsdk.AFUNC_OK
            
        self.ui.destroy(self.panel)
        
        return lwsdk.AFUNC_OK
    Regards,
    KANUSO
    Last edited by KANUSO; 04-11-2018 at 09:04 AM.

  4. #4
    Super Member JohnMarchant's Avatar
    Join Date
    Mar 2003
    Location
    Murcia, Spain
    Posts
    3,006
    I really must find the time to get up to speed with Python.
    Dell XPS 15
    15.6-inch (3840 x 2160) 4K 282ppi IPS LCD
    Intel i7 7700HQ 2.8GHz
    Windows 10 64Bit
    NVidia GeForce GTX 1050 Ti
    1TB SDD
    32Gb Ram

    LightWave 2019.03

    Very nice Laptop

  5. #5
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127

    Garbage investigation

    If someone want to look for leaks, I found the following useful.

    Code:
    # at the top of the script
    Import gc
    
    # somewhere in the code (the place where you are looking for garbage)
    gc.enable()
    gc.set_debug(gc.DEBUG_LEAK)
    gc.collect()
    for line in gc.garbage:
        s=str(type(line)) + '   ' + str(line)
        # s can get very Long, so you may want to cut it before you print it out
        print s
    This prints all "hanged" garbage (leak)
    As with this I noticed, that Python-scripts based on lwsdk.ICommandSequence will leave garbage on every call. Other baseclasses I have not tested until now.
    If I am sure, that this is a issue of lwsdk, I will file a Report to NT, at the Moment I am investigating where the leak Comes from.
    But even the gears.py example from NT produces this garbage

    Regards,
    Kanuso

  6. #6
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127

    minimalize garbage of ICommandSequence derived classes

    If this behaves on other Interfaces of lwsdk? I do not know, I have not tested other interfaces.

    Here a very minimalistic ICommandSequence derived app

    Code:
    # -*- Mode: Python -*-
    # -*- coding: ascii -*-
    
    """
    This is a LightWave Command Sequence plug-in (Modeler)
    """
    
    import lwsdk
    
    __author__     = "Author"
    __date__       = "Date"
    __copyright__  = "Copyright"
    __version__    = "1.0"
    __maintainer__ = "Maintainer"
    __email__      = "[email protected]"
    __status__     = "Example"
    __lwver__      = "2018"
    
    class minimal(lwsdk.ICommandSequence):
        def __init__(self, context):
            super(minimal, self).__init__()
    
        # LWCommandSequence -----------------------------------
        def process(self, mod_command):
    
            return lwsdk.AFUNC_OK
    
    
    ServerTagInfo = [
                        ( "Python Minimal", lwsdk.SRVTAG_USERNAME | lwsdk.LANGID_USENGLISH ),
                        ( "Minimal", lwsdk.SRVTAG_BUTTONNAME | lwsdk.LANGID_USENGLISH ),
                        ( "Utilities/Python", lwsdk.SRVTAG_MENU | lwsdk.LANGID_USENGLISH )
                    ]
    
    ServerRecord = { lwsdk.CommandSequenceFactory("LW_PyMinimal", minimal) : ServerTagInfo }
    This will do nothing but producing garbage for every time you call it.
    To demonstrate this do the following:
    Open the Python console
    type in:
    import gc
    gc.enable()
    gc.set_debug(gc.DEBUG_LEAK)


    Now run the minimal app from the above for example 5 times


    Now go to the Python console and go in to its multiline mode (by clicking the arrow button at the bottom right)
    now enter in the multiline box the followin:

    Code:
    gc.collect()
    for x in gc.garbage:
        s=str(x)
        print type(x),s
    This will show the garbage that "minimal" leaves behind.


    Now we see, that in this garbage is a line for every function, that is defined in "minimal".
    And that is what every ICommandSequence derived class will leave behind on every time it is executed.
    As if we know this, we can write an dedicated prg-class and call it in the process function of the ICommandSequence derived class.
    With this in mind we can reduce the garbage to the minimum possible.

    I do not know if this is a thing of Python, or if this comes from the lwsdk.

    Can anyone give a comment on this?
    Should this be reported to NT?
    Is this all a thing of the gc-library itself?

    Regards,
    Kanuso

  7. #7
    Registered User
    Join Date
    May 2017
    Location
    Lampertheim, Germany
    Posts
    127
    If you write
    Code:
    import gc
    
    ...
    
    ...
    
    def process(self, mod_command):
        gc.enable()
        ... do your work ...
        gc.collect()
        return lwsdk.AFUNC_OK
    the strange function references to the functions in the derived class seems to be freed. There will be still unreachable objects per function, but they are freed during the gc.collect() call

    Yes, I'am aware that you should do a del gc.garbage[:] in the previouse posts, but even if you do so, the strange garbage is available.
    For now I will place a gc.enable() in every scripts startup and a gc.collect in every scripts cleanup. So at last the "normal" garbage is freed.

    Regards,
    Kanuso

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •