Some how to's

KANUSO

New member
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.

View attachment 141079

Regards,
KANUSO
 

Attachments

  • nuArchitectPy.zip
    35.6 KB · Views: 375
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:
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:
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
 
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
 
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
 
Back
Top