Isotropix Forums

I'm discovering Python :) Noob blog !

Clarisse Scripting related topics

I'm discovering Python :) Noob blog !

Unread postby daaims » Fri Oct 07, 2016 2:04 pm

Hi,

I make this post to share my advancement in scripting, share tips and others things concerning scripting.

As you will see i'm a full virgin in Python or other computer language so don't hit me too hard :mrgreen:

My first working script is to scatter a geometry on another by selecting the object you want to scatter first and then the object you want to scatter on with naming the created object. The script desactivate ParentInPlace temporaly to parent the PTC to the target and reactivate it after or let it desactivated if it was desactivated. So it doesn't change the user's prefs...

Code: Select all
source = ix.selection[0]
target = ix.selection[1]
names = source.get_name()
namet = target.get_name()

if not source.is_kindof("Geometry") or not target.is_kindof("Geometry"):
    window = ix.api.GuiWindow(ix.application, 600, 600, 200, 100)
    label = ix.api.GuiLabel(window, 0, 0, 200, 100, "You fucked up !")
    label.set_constraints(ix.api.GuiWidget.CONSTRAINT_LEFT,\
                       ix.api.GuiWidget.CONSTRAINT_TOP,\
                       ix.api.GuiWidget.CONSTRAINT_RIGHT,\
                       ix.api.GuiWidget.CONSTRAINT_BOTTOM)
    label.set_justification(ix.api.GuiWidget.JUSTIFY_CENTER)
    window.show()
    while window.is_shown(): ix.application.check_for_events()
    ix.log_error("You want scatter another object than a geometry !!!")

# -----------------------------------------------

ptc = ix.cmds.CreateObject("PTC_" + names, "GeometryPointCloud")

pip = ix.application.get_prefs().get_bool_value("layout", "parent_in_place")

if pip == True:
    ix.cmds.SetParentInPlace(False)
    ix.cmds.SetParent([str(ptc) + ".parent"], [target], [0])
    ix.cmds.SetParentInPlace(True)
else:
    ix.cmds.SetParent([str(ptc) + ".parent"], [target], [0])

ix.cmds.SetValues([str(ptc) + ".geometry"], [target])
ix.cmds.SetValues([str(ptc) + ".point_count"], ["100"])

sct = ix.cmds.CreateObject("SCT_" + names, "SceneObjectScatterer")
ix.cmds.SetValues([str(sct) + ".geometry_support"], [ptc])
ix.cmds.AddValues([str(sct) + ".geometry"], [source])



Now i want to go farther and scatter multiples objects on the last selected.

I begin with this :

sel = ix.selection
count = sel.get_count()
first = sel[0]
last = sel[count-1]


And then i'm stucked.
First, i don' t understand why i can't write last = sel[-1] instead of last = sel[count-1] to store the last selected object.
Second, i don't achieve to store X-last objects in my selection where X is the "list"? of selected object. I've tried sources = sel[0:count-2] but i got an error :roll:

I want to finish the execution of the code after closing the window wich pop if not source.is_kindof("Geometry") or not target.is_kindof("Geometry"):
the only way i found for now is ix.log_error(). Is there another solution ?

To finish i don't get the difference between ix.selection and ix.application.get_selection()

See ya, Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Fri Oct 14, 2016 10:02 am

Hi,

Something strange is happening
Code: Select all
ix.cmds.CreateObject("Volume_Box", "GeometryVolumeBox")
ix.cmds.CreateObject("Mtl_Volume", "MaterialVolume")
ix.cmds.SetValues(["project://Volume_Box.materials"], ["project://Mtl_Volume"])


When i execute this code the material is not set to the volume box. But if i execute the last line when the two object are created it works...

Any idea ?

Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby halim » Mon Oct 17, 2016 12:51 pm

Hello,

Please find our answers bellow quotes:
daaims wrote:And then i'm stucked.
First, i don' t understand why i can't write last = sel[-1] instead of last = sel[count-1] to store the last selected object.
Second, i don't achieve to store X-last objects in my selection where X is the "list"? of selected object. I've tried sources = sel[0:count-2] but i got an error

ix.selection returns an ApplicationSelection clarisse object which has not exactly the same properties as a regular python list so it's fairly normal it doesn't comply all a regular python list should.

You can simply create a python list and populate it with objects stored in ApplicationSelection,
Code: Select all
ix_sel = ix.selection
sel = []
for obj in ix_sel:
    sel.append(obj)   
count = len(sel)
first = sel[0]
last = sel[-1]

This way the sel python list object will give you access to all you are used to.

daaims wrote:I want to finish the execution of the code after closing the window wich pop if not source.is_kindof("Geometry") or not target.is_kindof("Geometry"):
the only way i found for now is ix.log_error(). Is there another solution ?

No sure to understand what you want to do, popup a dialog message ? You have several ways to do so and you don't necessarily have to log an error using ix.logerror.

daaims wrote:To finish i don't get the difference between ix.selection and ix.application.get_selection()

ix.selection is an ApplicationSelection instance object clarisse gives you access through python.
ix.application.get_selection() is an exposed method to python that returns a proxy of the AppSelection object.
At the end, both will give you similar objects, ix.selection feels a little more straightforward since it's a direct access to an object that exists in memory and not a method that builds and returns it.

daaims wrote:Something strange is happening
Code: Select all
ix.cmds.CreateObject("Volume_Box", "GeometryVolumeBox")
ix.cmds.CreateObject("Mtl_Volume", "MaterialVolume")
ix.cmds.SetValues(["project://Volume_Box.materials"], ["project://Mtl_Volume"])


When i execute this code the material is not set to the volume box. But if i execute the last line when the two object are created it works...

Any idea ?

Cheers ;)

Those commands won't work unless you are in the root project context while you are executing them.
Each CreateObject command returns the created object so this way you can write a project location agnostic script:
Code: Select all
vbox = ix.cmds.CreateObject("Volume_Box", "GeometryVolumeBox")
vmat = ix.cmds.CreateObject("Mtl_Volume", "MaterialVolume")
ix.cmds.SetValue(str(vbox)+".materials", [str(vmat)])

Which will assign the material on the spot, no matter where you are in the current project while executing it.

Hope this helps.
Halim Negadi
Technical Artist - Clarisse Specialist
User avatar
halim
 
Posts: 141
Joined: Thu Nov 21, 2013 8:27 pm

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Mon Oct 17, 2016 2:34 pm

halim wrote:
daaims wrote:I want to finish the execution of the code after closing the window wich pop if not source.is_kindof("Geometry") or not target.is_kindof("Geometry"):
the only way i found for now is ix.log_error(). Is there another solution ?

No sure to understand what you want to do, popup a dialog message ? You have several ways to do so and you don't necessarily have to log an error using ix.logerror.


I want to simply stop the execution of a script at line X.

Code: Select all
a = popo
b = momo
if a < b
    do this
    do that
    A command to stop the script here
ix.cmds.CreateObject(blabla)
ix.cmds.CreateObject(blibli)
.....
...


halim wrote:
daaims wrote:Something strange is happening
Code: Select all
ix.cmds.CreateObject("Volume_Box", "GeometryVolumeBox")
ix.cmds.CreateObject("Mtl_Volume", "MaterialVolume")
ix.cmds.SetValues(["project://Volume_Box.materials"], ["project://Mtl_Volume"])


When i execute this code the material is not set to the volume box. But if i execute the last line when the two object are created it works...

Any idea ?

Cheers ;)

Those commands won't work unless you are in the root project context while you are executing them.


Yeah i know, but i meant those commands don't work even if they are executed in the root context :P (Perhaps a bug ? Tested on 3.0 SP3 and the last Pegasus release)
When in root context : Execute it one time will create Volume_Box and Mtl_Volume but don't set Mtl_Box to Volume_Box.
Execute it a second time will create Volume_Box1 and Mtl_Volume1 and now set Mtl_Volume to Volume_Box

For the other things thanks a lot, i understand a bit more the basics now !

Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Thu Oct 27, 2016 11:40 am

Hi,

I've made a toggle button for ParentInPlace. I want the icon change when PiP is OFF and PiP is ON. That is what i wrote :

Code: Select all
pip = ix.application.get_prefs().get_bool_value("layout", "parent_in_place")
item = ix.application.get_shelf().get_items(0, "Geometry")[18]

if pip == True:
    ix.cmds.SetParentInPlace(False)
    ix.application.message_box("Parent in Place set to : OFF", "Information", ix.api.AppDialog.ok(), ix.api.AppDialog.STYLE_OK)
    item.set_icon_filename("\\\\192.168.20.3\ZTransfert\_Daims\Clarisse\_Shelves\Icons\\110_TogglePiP.png")
if pip == False:
    ix.cmds.SetParentInPlace(True)
    ix.application.message_box("Parent in Place set to : ON", "Information", ix.api.AppDialog.ok(), ix.api.AppDialog.STYLE_OK)
    item.set_icon_filename("\\\\192.168.20.3\ZTransfert\_Daims\Clarisse\_Shelves\Icons\\111_TogglePiP.png")


The filename is set but i have to restart clarisse to see the icon change. Not really accurate for a "toggle icon change" :roll:
Is there a way to refresh the shelves toolbar or something ?

Otherthing, is there a way to get ONE item from the shelves through the title instead of get_items(0, "Geometry")[18] ?

Thanks

Edit : when i change the slot view style the icon change, so i guess i have to redraw the shelves, but how can i make this ?

Thanks
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Thu Nov 10, 2016 3:27 pm

Hi,

I want to share you a "ShelfItemLoader" script. This script look in specified folder(s) and create a shelf item when it found a .py file, name it and set a description to it.

Code: Select all
shelf = ix.application.get_shelf()
slotNumber = 0
cat = "Geometry"

itemsPath = "\\\\192.168.20.3\ZTransfert\_Daims\Clarisse\_Shelves\geometry\\"
iconPath = "\\\\192.168.20.3\ZTransfert\_Daims\Clarisse\_Shelves\Icons\\"

import os
dir = "\\\\192.168.20.3\ZTransfert\_Daims\Clarisse\_Shelves\geometry"
os.chdir(dir)
files = os.listdir(dir) # list files in dir directory

list = []
names = []

for file in files:
    ext = file.split(".")[-1]
    if ext == "py": # test if the file extension is python to avoid appending wrong files
        noExt = ".".join(file.split(".")[:-1])
        list.append(noExt)
        name = noExt.split("_")[-1]
        names.append(name) # appending file name without extention and prefixe to set item name at the shelf item creation

def is_item_exists(slot, category_name, item_title):
    items = shelf.get_items(slot, category_name)
    if items == None:
        return False

    for j in range(items.get_count()):
        item = items[j]
        if item_title == item.get_title():
            return True
    return False

if is_item_exists(slotNumber, cat, names[0]) == False:
    shelf.add_separator(slotNumber, cat)
    print "Tools have been loaded in Geometry Shelf :)"
else:
    print "Tools are already loaded in Geometry Shelf :)"

# shelf items creation with name and description
for i in range(len(list)):
    if is_item_exists(slotNumber, cat, names[i]) == False:
        with open(files[i]) as file:
            desc = file.readline()
        shelf.add_item(slotNumber, cat, names[i], desc, itemsPath+str(list[i])+".py", iconPath+str(list[i])+".png")
        if list[i].split("_")[-1] in ("RenderRig", "Scatterer", "VolumeBox", "SetTexture", "MatteMaterial"):
            shelf.add_separator(slotNumber, cat) # add separator after wanted item



I set a specific syntax to my .py fileS and icon fileS to get the shelf items alphabetically sorted.
ShelfItemName.jpg
ShelfItemName.jpg (99.17 KiB) Viewed 7302 times


To add a new shelf item just copy your new .py file into your shelfitems folder with its correspondant icon and re-execute the script. The .py file and the icon file must have the same name.
The new item take the name of the .py file without extention and prefixe and the description is read from the first line of the .py file, it mean this first line has to be commented. For example :

Code: Select all
# Combine the selected objects

ix.enable_command_history()
sel = ix.selection
count = sel.get_count()
cbn = ix.cmds.CreateObject("CBN_", "SceneObjectCombiner")

if count > 0:
    for i in range(count):
        obj = sel[i].get_full_name()
        ix.cmds.AddValues([str(cbn) + ".objects"], [obj])
ix.disable_command_history()


I set the execution of this script in the startup_script.py file so everytime i launch Clarisse the ShelfItem folder is re-seeked for new items and new items are loaded.

Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Wed Nov 23, 2016 10:13 pm

Hi,

Today i want to share you a light scatterer.

Image

Code: Select all
firstFrame = 1
lastFrame = 100
light = "LightPoint"
lightName = "Point_"

ix.cmds.SetCurrentFrame(firstFrame)

sel = ix.application.get_selection()
if sel.get_count() == 0:
    ix.application.message_box("You must select a Point Cloud !", "Information", ix.api.AppDialog.ok(), ix.api.AppDialog.STYLE_OK)
if not sel.get_item(0).is_kindof("Geometry"):
    ix.application.message_box("You must select a Point Cloud !", "Information", ix.api.AppDialog.ok(), ix.api.AppDialog.STYLE_OK)

sel = ix.selection[0]
ptc = sel.get_module().get_geometry().get_point_cloud()
ptc_count = ptc.get_point_count()
lgtList = []

for i in range(ptc_count):
    lgt = ix.cmds.CreateObject(lightName, light)
    lgtList.append(lgt.get_full_name())

while firstFrame <= lastFrame:
    T = ix.application.get_factory().get_vars().get("T").get_double()
    ptc = ix.get_item(str(sel)).get_module().get_geometry().get_point_cloud() # force to evaluate the ptc at actual frame
    global_matrix = sel.get_module().get_global_matrix()
    pos = ix.api.GMathVec3fArray()
    pos_new = ix.api.GMathVec3d()
    for i in range(ptc_count):
        ptc.get_positions(pos)
        ix.api.GMathMatrix4x4d.multiply(pos_new,pos[i],global_matrix)
        lgto = ix.get_item(lgtList[i])
        lgtStr = str(lgto)
        ix.cmds.SetKey([lgtStr + ".translate[0]", lgtStr + ".translate[1]", lgtStr + ".translate[2]"], T, [pos_new[0], pos_new[1], pos_new[2]], 0)
    firstFrame = firstFrame + 1
    ix.cmds.SetCurrentFrame(firstFrame)
else:
    ix.application.message_box("Lights baking complete !", "Information", ix.api.AppDialog.yes(), ix.api.AppDialog.STYLE_OK)


It's not really a light scatterer but a light animation baker on animated point cloud :mrgreen: Just select the point cloud you want lights to be scattered and animated on, choose a range, the light type and run it. (by default, range is 1 - 100 and point light are created).

Next update will be a windows pop up to choose range and light type !

Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Mon Jan 09, 2017 11:34 am

Hi there !

Here is an update of the light scatterer script. You can choose the frame range and the type of light you want to bake on with nice UI.

UI_LightScatterer.jpg
UI_LightScatterer.jpg (18.59 KiB) Viewed 3310 times


Code: Select all
'''
------------------------------------------------------------------------------------------------------------------------------------------------------------
INIT
------------------------------------------------------------------------------------------------------------------------------------------------------------
'''
sel = ix.application.get_selection()
if sel.get_count() == 0:
    ix.log_error("You have to select a Point Cloud !\n")
if not sel.get_item(0).is_kindof("Geometry"):
    ix.log_error("You have to select a Point Cloud !\n")


sel = ix.selection[0]
ptc = sel.get_module().get_geometry().get_point_cloud()
ptc_count = ptc.get_point_count()
lgtList = []

#Define the default values
result = {'firstFrame' : 0, 'lastFrame' : 0, 'type' : [' - ','LightPoint','LightDistant','LightSpot','LightArea','LightLinear']}

'''
------------------------------------------------------------------------------------------------------------------------------------------------------------
UI CLASS
------------------------------------------------------------------------------------------------------------------------------------------------------------
'''
class EventRewire(ix.api.EventObject):
    #These are the called functions by the connect. It is more flexible to make a function for each button
    def firstFrameRefresh(self, sender, evtid):
        result['firstFrame'] = sender.get_value() #refresh result

    def lastFrameRefresh(self, sender, evtid):
        result['lastFrame'] = sender.get_value() #refresh result

    def listRefresh(self, sender, evtid):
        result['type'] = sender.get_selected_item_name() #refresh result

    def cancel(self, sender, evtid):
        sender.get_window().hide() #Hide the window, if it is done, the window is destroy

    def run(self, sender, evtid):
        #Put here the core of your script (or a function to separate UI and Script)
        if result['type'] == ' - ':
            ix.log_error("You have to select a light type")

        sender.get_window().hide() #Hide the window, if it is done, the window is destroy

        firstFrame = result['firstFrame']
        lastFrame = result['lastFrame']
        light = result['type']
        lightName = result['type'][5:] + "_1"

        ix.cmds.SetCurrentFrame(firstFrame)

        for i in range(ptc_count):
            lgt = ix.cmds.CreateObject(lightName, light)
            lgtList.append(lgt.get_full_name())

        while firstFrame <= lastFrame:
            T = ix.application.get_factory().get_vars().get("T").get_double()
            ptc = ix.get_item(str(sel)).get_module().get_geometry().get_point_cloud() # force to evaluate the ptc at actual frame
            global_matrix = sel.get_module().get_global_matrix()
            pos = ix.api.GMathVec3fArray()
            pos_new = ix.api.GMathVec3d()
            for i in range(ptc_count):
                ptc.get_positions(pos)
                ix.api.GMathMatrix4x4d.multiply(pos_new,pos[i],global_matrix)
                lgto = ix.get_item(lgtList[i])
                lgtStr = str(lgto)
                ix.cmds.SetKey([lgtStr + ".translate[0]", lgtStr + ".translate[1]", lgtStr + ".translate[2]"], T, [pos_new[0], pos_new[1], pos_new[2]], 0)
            firstFrame = firstFrame + 1
            ix.cmds.SetCurrentFrame(firstFrame)
        else:
            ix.application.message_box("Baking Lights complete !", "Information", ix.api.AppDialog.yes(), ix.api.AppDialog.STYLE_OK)

'''
------------------------------------------------------------------------------------------------------------------------------------------------------------
UI GENERATION
------------------------------------------------------------------------------------------------------------------------------------------------------------
'''
#Window creation
clarisse_win = ix.application.get_event_window()
window = ix.api.GuiWindow(clarisse_win, 900, 450, 240, 160) #Parent, X position, Y position, Width, Height
window.set_title('Set values...') #Window name

#Main widget creation <= this is the correct way to make a GUI, make a default widget and add inside what you want
panel = ix.api.GuiPanel(window, 0, 0, window.get_width(), window.get_height())
panel.set_constraints(ix.api.GuiWidget.CONSTRAINT_LEFT, ix.api.GuiWidget.CONSTRAINT_TOP, ix.api.GuiWidget.CONSTRAINT_RIGHT, ix.api.GuiWidget.CONSTRAINT_BOTTOM)

#Form generation
firstFrame = ix.api.GuiNumberField(panel, 79, 10, 151, "Fist Frame :  ")#Clarrisse_function(parent, X_position, Y_position, Width, Height, "label", 'this is a custom param to get the value without duplicate the CLASS')
firstFrame.set_increment(1) # this param force the increment ofNumberField to 1

lastFrame = ix.api.GuiNumberField(panel, 79, 40, 151, "Last Frame : ")#Clarrisse_function(parent, X_position, Y_position, Width, Height, "label", 'this is a custom param to get the value without duplicate the CLASS')
lastFrame.set_increment(1) # this param force the increment ofNumberField to 1

listLabel  = ix.api.GuiLabel(panel, 10, 70, 50, 22, "Type : ") #Clarrisse_function(parent, X_position, Y_position, Width, Height, "label")
list = ix.api.GuiListButton(panel, 70, 70, 160, 22) #Make an object of your class with the param inside __init__(parent, X, Y, Width, Height)
for i in result['type']:
    list.add_item(i) #add_item('the_name_of_your_item')

cancelBtn = ix.api.GuiPushButton(panel, 10, 130, 100, 22, "Cancel") #The cancel button (destroy the script window)
runBtn = ix.api.GuiPushButton(panel, 130, 130, 100, 22, "Run") # The run button to run your script

#init values
firstFrame.set_value(result['firstFrame']) #set the default value to the firstFrame input
lastFrame.set_value(result['lastFrame']) #set the default value to the lastFrame input

#Connect to function
event_rewire = EventRewire() #init the class

event_rewire.connect(firstFrame, 'EVT_ID_NUMBER_FIELD_VALUE_CHANGING', event_rewire.firstFrameRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(firstFrame, 'EVT_ID_NUMBER_FIELD_VALUE_CHANGED', event_rewire.firstFrameRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(lastFrame, 'EVT_ID_NUMBER_FIELD_VALUE_CHANGING', event_rewire.lastFrameRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(lastFrame, 'EVT_ID_NUMBER_FIELD_VALUE_CHANGED', event_rewire.lastFrameRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(list, 'EVT_ID_LIST_BUTTON_SELECT', event_rewire.listRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(list, 'EVT_ID_LIST_BUTTON_SELECT_UNCHANGED', event_rewire.listRefresh) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(cancelBtn, 'EVT_ID_PUSH_BUTTON_CLICK', event_rewire.cancel) #connect(item_to_listen, what_we_are_listening, function_called)
event_rewire.connect(runBtn, 'EVT_ID_PUSH_BUTTON_CLICK', event_rewire.run) #connect(item_to_listen, what_we_are_listening, function_called)

#Send all info to clarisse to generate window
window.show()
while window.is_shown():    ix.application.check_for_events()
window.destroy()


Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Re: I'm discovering Python :) Noob blog !

Unread postby atnreg » Thu Jan 19, 2017 5:41 pm

Great!

But how the %&# did you find out how to use the listbutton?
The SDK 'manual' is only auto-generated list of classes, no real descriptions for most of the methods (and nothing for Python) and only example (and in C++, not Python!) is the progress_bar :(

How did you guess that you can simply use add_item with simple text when the SDK gives REALLY complicated signature for add_item?? :o :o

I would need to make a listbox that I can fill with text items and thanks to your example the same method works for listview :)
But that's all that works. I cannot select any item, not even one, it gets deselected as soon as I move mouse. And I have no idea how to react to selection. I would need to be able to select multiple items with normal shift/control-clicks but I have no clue how to do that. Best would be checkboxes for each item but I think that is way too complicated without documentation.

Can you or someone else please help? :)
Did you find some secret documentation or how did you get that far so fast? :o

Isotropix, we need A LOT more documentation and simple examples for Clarisse Python GUI! :geek:

Thanks!

Antti
Intel i7(6-core),32GB RAM,NVIDIA GTX690 (2GPU)+GTX Titan Black,Win10Pro 64bit
Clarisse 3.5SP2,Blender,ZBrush,Onyx,(3D-Coat,Lightwave+Octane...)
Clarisse since 2016-09-29 (Py 2017-01-04), Python since 2016-11-10
I do all 3D stuff for fun, no business
atnreg
 
Posts: 254
Joined: Mon Sep 19, 2016 5:20 pm
Location: Helsinki, Finland

Re: I'm discovering Python :) Noob blog !

Unread postby daaims » Fri Jan 20, 2017 10:10 am

Hi,

No secret documentation for me, i just ask around like is this topic for the button and pick some code here and there in the forum.

Cheers ;)
User avatar
daaims
 
Posts: 431
Joined: Fri Mar 22, 2013 1:17 am

Next

Return to Scripting