View Full Version : Some how to's

04-07-2018, 07:31 AM
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:

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.



04-11-2018, 01:53 AM
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.

# 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
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

# 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

# to get the text of an individual cell

# to get the actual selected row

# to set the actual selected row

# to create the listbox all dimensions and positions are pixel coordinates
self.lbox=self.panel.multilist_ctl(title,width,hei ght,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.


04-11-2018, 09:01 AM
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)

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:
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.mousewhee l)# 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:
return lwsdk.AFUNC_OK


return lwsdk.AFUNC_OK


04-12-2018, 03:43 AM
I really must find the time to get up to speed with Python.

12-12-2018, 09:17 AM
If someone want to look for leaks, I found the following useful.

# at the top of the script
Import gc

# somewhere in the code (the place where you are looking for garbage)
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


12-15-2018, 03:22 AM
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

# -*- 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

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:

for x in gc.garbage:
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?


12-16-2018, 05:06 AM
If you write

import gc



def process(self, mod_command):
... do your work ...
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.