Part 4 – On Event Plugins

2

If you have worked much with any programming language you should be familiar with Triggers/Events/Callbacks or one of the other many names for them. If not, don’t worry; Events in Xsi are really easy. Basically an On Event plugin is run when a certain event takes place. So if we create a “siOnSelectionChange” Event, the plugin will be run every time change what is selected i.e. by selecting something else.

So to illustrate this I’m going to create two cubes, a Polygon Mesh cube and a Surface cube. If I set my Viewport to dislpay Shaded, the two cubes look exactly the same. What I want is a quick way to tell what “Type” the object I have selected is so I can easily tell if it’s a Polygon Mesh, Surface or whatever else. For this we want our plugin to check what Type the selection is every time we change what we have selected, so the “siOnSelectionChange” Event will be perfect for this.

To create an On Event plugin you start the same way as every other plugin.

Click File >> Plug-in Manager >> File >> New >> Event

Then we setup the plugin. I’ve set the name to “SelectedType_SelectionEvent” so that just by looking at the plugin name I get an idea of what it does.

After that click on the “Event Definition” tab to select the event you want. Near the bottom you’ll find the “siOnSelectionChange” Event.

Once you’ve checked that, scroll to the top and hit the “Generate Code” button.

To get the selected object’s Type we can do something like:

# Get a the selected objects and store in a list called "selected_lst"
selected_lst = Application.Selection

# If no objects are selected
if len(selected_lst) == 0:
    Application.LogMessage("No Selection")

# If there is more than one object selected
elif len(selected_lst) > 1:
    Application.LogMessage("Multi Selection")

# Otherwise there must only be one object selected
else:
    # Note if I want to get the first item in a list I use my_list[0] and not my_list[1]
    # A list's order actually goes [0, 1, 2, 3]
    # So if I have a list ["banana", "apple", "pear", "grape"], "pear" is at position [2]
    selected_obj = selected_lst[0]
    # Log the type of the selected object
    Application.LogMessage(selected_obj.Type)

 

And just like with the commands, all we need to do is add our code to the plugin’s “_Execute” function and then save the plugin.

So now when I select an object, the plugin tells me what I’ve got selected:

And as soon as I select something else or even if I select nothing the plugin will detect the selection has changed and Log what ever we told it to.
The entire plugin looks like this:

# SelectedType_SelectedEvent
# Initial code generated by Softimage SDK Wizard
# Executed Wed Aug 1 17:57:30 UTC+0200 2012 by jared.glass
#
# Tip: To add a command to this plug-in, right-click in the
# script editor and choose Tools > Add Command.
import win32com.client
from win32com.client import constants

null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
    in_reg.Author = "jared.glass"
    in_reg.Name = "SelectedType_SelectionEvent"
    in_reg.Major = 1
    in_reg.Minor = 0

    in_reg.RegisterEvent("siOnSelectionChangeEvent",constants.siOnSelectionChange)
    #RegistrationInsertionPoint - do not remove this line

    return true

def XSIUnloadPlugin( in_reg ):
    strPluginName = in_reg.Name
    Application.LogMessage(str(strPluginName) + str(" has been unloaded."),constants.siVerbose)
    return true
    # Callback for the siOnSelectionChangeEvent event.

def siOnSelectionChangeEvent_OnEvent( in_ctxt ):
    Application.LogMessage("siOnSelectionChangeEvent_OnEvent called",constants.siVerbose)

    Application.LogMessage("ChangeType: " + str(in_ctxt.GetAttribute("ChangeType")),constants.siVerbose)

    # Get a the selected objects and store in a list called "selected_lst"
    selected_lst = Application.Selection

    # If no objects are selected
    if len(selected_lst) == 0:
        Application.LogMessage("No Selection")

    # If there is more than one object selected
    elif len(selected_lst) > 1:
        Application.LogMessage("Multi Selection")

    # Otherwise there must only be one object selected
    else:
        # Note if I want to get the first item in a list I use my_list[0] and not my_list[1]
        # A list's order actually goes [0, 1, 2, 3]
        # So if I have a list ["banana", "apple", "pear", "grape"], "pear" is at position [2]
        selected_obj = selected_lst[0]
        # Log the type of the selected object
        Application.LogMessage(selected_obj.Type)

    # Return value is ignored as this event can not be aborted.
    return true

 

Part 3 – Plugins that take Input and give Output.

2

Today I’m going to start backwards.

Output:

To set a plugin to give Output is really, really easy. All we need to do is change the line at the end of the plugin’s “_Execute( )” function from:

return true

to

return [Whatever we want]

We can return a string, an object, a list, a list of objects and pretty much every other python data structure we could ever want.

The reason I may want a plugin to give output is if I have a scenario where I need to do something, and some of the code is already in the plugin but the same plugin is also used by other tools so I can’t change the actual way the plugin works or other tools may break.

So for this example what I want to do is to select all the polymeshes in my scene except any called “cube”. Now I already have a plugin that selects all the polymeshes in my scene (called SelectAll_PolyMeshes). So all I need to do is to get that list and see if there are any objects called “cube” in it and if so, remove them from the selection. Simple 🙂

First I need to set my SelectAll_PolyMeshes command to output (return)  the list of polymeshes so the plugin looks like:

# SelectAll_PolyMeshesPlugin
# Initial code generated by Softimage SDK Wizard
# Executed Thu Jul 19 18:18:51 UTC+0200 2012 by jared.glass
#
# Tip: To add a command to this plug-in, right-click in the
# script editor and choose Tools > Add Command.
import win32com.client
from win32com.client import constants

null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
    in_reg.Author = "jared.glass"
    in_reg.Name = "SelectAll_PolyMeshesPlugin"
    in_reg.Major = 1
    in_reg.Minor = 0

    in_reg.RegisterCommand("SelectAll_PolyMeshes","SelectAll_PolyMeshes")
    #RegistrationInsertionPoint - do not remove this line

    return true

def XSIUnloadPlugin( in_reg ):
    strPluginName = in_reg.Name
    Application.LogMessage(str(strPluginName) + str(" has been unloaded."),constants.siVerbose)
    return true

def SelectAll_PolyMeshes_Init( in_ctxt ):
    oCmd = in_ctxt.Source
    oCmd.Description = ""
    oCmd.ReturnValue = true

    return true

def SelectAll_PolyMeshes_Execute( ):

    Application.LogMessage("SelectAll_PolyMeshes_Execute called",constants.siVerbose)

    # Find all the polymeshes in the scene and store them in a python list.
    polymesh_lst = list(Application.ActiveSceneRoot.FindChildren("*", "polymsh"))

    # Create a list to store the meshes names in
    polymeshName_lst= []
    # Loop through the mesh objects and store their names
        for polymesh in polymesh_lst:
            # Add the meshes name to the mesh name list
            polymeshName_lst.append(polymesh.FullName)

    # Join the names in the list into a string and sepperate each name by a comma. eg "sphere, cone, cube"
    polymeshSel_str = ", ".join(polymeshName_lst)

    # Tell Xsi to select the names in the polymeshSel_str
    Application.Selection.SetAsText(polymeshSel_str)

    # The return that we will change
    return true

Now, we want the list of polymesh objects to check for “cube” in the name and then to remove the object from the selection. All we need to do is to change the

return true

right at the end to

return polymesh_lst

After you’ve added that save the “”SelectAll_PolyMeshes” plugin to update it in Xsi.

now if we:

print Application.SelectAll_PolyMeshes()

we should get something that looks like:

# [<COMObject <unknown>>,  <COMObject <unknown>>, <COMObject <unknown>>, <COMObject <unknown>>]

Thats the list of Xsi objects.

From that I can do

polymesh_lst = Application.SelectAll_PolyMeshes()

instead of printing it so I can actually store the data in a list.

From here the rest is cake. So in the end my code looks like this :

    # Get the list of polymeshes from the plugin.
    polymesh_lst = Application.SelectAll_PolyMeshes()
    # Loop through the polymeshes and check the names.
    for polymesh in polymesh_lst:
        # Check if the polymeshe's name is cube
        if polymesh.name == "cube":
            # If it is, remove it from the selection
            Application.Selection.Remove(polymesh)

Input:

(Also called Arguments)

Setting up a plugin to take input is just as easy, it just requires a few more lines of code to be replaced. For this example we’re going to change the “SelectAll_PolyMeshes” Plugin, but in such a way that it won’t change the way it works with any of the other tools that may use it (like the one we just made that removes cube from the selection). This is a lot easier than it sounds. So we’re going to set the plugin to take in part of an objects name, and only if that part of the name is in a PolyMeshe’s name, does the PolyMesh get selected.

So we will be able to call the command Application.SelectAll_PolyMeshes()

like Application.SelectAll_PolyMeshes("part_of_some_object_name")

First thing we need to do is to add a variable to the plugin’s “Init” (The Init is run when the plugin is initially compiled by Xsi – When you launch Xsi or Reload the plugin by Saving it from the Script Editor). This variable will tell Xsi that the plugin takes some input.

So in the “SelectAll_PolyMeshes_Init” we add:

oArgs = oCmd.Arguments
oArgs.Add([Argument Name],constants.siArgumentInput,[Default Value])

I’m going to call the argument “part_of_name” and the default will be None.

This means that if something just calls Application.SelectAll_PolyMeshes(), part_of_name will be set to None and I can tell the plugin to ignore the part of name search and run as it had before my changes.

Next bit is to pass the argument (part_of_name) into the _Execute function. This will allow us to use part_of_name inside the _Execute.

After this I can edit the actual code within the _Execute as I please:

    # Loop through the mesh objects and store their names
    for polymesh in polymesh_lst:
        # If the part_of_name was specified and therefore is not the default None...
        if not part_of_name == None:
            # If the part_of_name is in the object's name...
            if part_of_name in polymesh.Name:
                # Add the meshes name to the mesh name list
                polymeshName_lst.append(polymesh.FullName)
            else:
                # Add the meshes name to the mesh name list
                polymeshName_lst.append(polymesh.FullName)

So my entire plugin looks like:

# SelectAll_PolyMeshesPlugin
# Initial code generated by Softimage SDK Wizard
# Executed Thu Jul 19 18:18:51 UTC+0200 2012 by jared.glass
#
# Tip: To add a command to this plug-in, right-click in the
# script editor and choose Tools > Add Command.
import win32com.client
from win32com.client import constants

null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
in_reg.Author = "jared.glass"
in_reg.Name = "SelectAll_PolyMeshesPlugin"
in_reg.Major = 1
in_reg.Minor = 0

in_reg.RegisterCommand("SelectAll_PolyMeshes","SelectAll_PolyMeshes")
#RegistrationInsertionPoint - do not remove this line

return true

def XSIUnloadPlugin( in_reg ):
    strPluginName = in_reg.Name
    Application.LogMessage(str(strPluginName) + str(" has been unloaded."),constants.siVerbose)
    return true

def SelectAll_PolyMeshes_Init( in_ctxt ):
    oCmd = in_ctxt.Source
    oCmd.Description = ""
    oCmd.ReturnValue = true

    oArgs = oCmd.Arguments
    oArgs.Add("part_of_name",constants.siArgumentInput,None)
    return true

def SelectAll_PolyMeshes_Execute( part_of_name ):

    Application.LogMessage("SelectAll_PolyMeshes_Execute called",constants.siVerbose)

    # Find all the polymeshes in the scene and store them in a python list.
    polymesh_lst = list(Application.ActiveSceneRoot.FindChildren("*", "polymsh"))

    # Create a list to store the meshes names in
    polymeshName_lst= []
    # Loop through the mesh objects and store their names
    for polymesh in polymesh_lst:
        # If the part_of_name was specified and therefore is not the default None...
        if not part_of_name == None:
            # If the part_of_name is in the object's name...
            if part_of_name in polymesh.Name:
                # Add the meshes name to the mesh name list
                polymeshName_lst.append(polymesh.FullName)
            else:
                # Add the meshes name to the mesh name list
                polymeshName_lst.append(polymesh.FullName)

    # Join the names in the list into a string and sepperate each name by a comma. eg "sphere, cone, cube"
    polymeshSel_str = ", ".join(polymeshName_lst)

    # Tell Xsi to select the names in the polymeshSel_str
    Application.Selection.SetAsText(polymeshSel_str)

    return polymeshName_lst

And if I want to select ALL the PolyMeshes I run Application.SelectAll_PolyMeshes()

but if I only want to select PolyMeshes with say “sphere” in the name, I run Application.SelectAll_PolyMeshes("sphere"). This sets the part_of_name variable from None to "sphere".

Note:

When creating a plugin you also get the option to add input arguments:

I do use this but I find I end up changing existing plugins that don’t take arguments (or that I want to take even more arguments) much more than I use this tool to create them.

Start Here!!!

0

Hi there 🙂

This Guide will cover Basic to Advanced Scripting, Programming, Plugin Development, Class Creation and much more. Including lots of handy titbits and explanations, like how to successfully use the Xsi help files. The goal being to equip you with the knowledge and tools you need to create awesome scripts, plugins and even external tools that speed up your workflow and make you a super programming genius!!! Which all results in your life being made a whole lot easier and giving you more time to spend on the artistic side of things, or you could even move over completely and work as a super programming genius. Either way this blog puts the power in your hands 😉

Hope you enjoy!
(And feel free to let me know what you think)

The Lessons are split up into sections:

  1. Recruit – Introduction to Scripting
  2. Trooper – The simple programming side of scripting
  3. Hardened – Basic Programming and Plugins
  4. Veteran – Classes, Modules, Threads and PyQt for Xsi.

And the Sections are made up of the following lessons:

Recruit:

  1. Easily turn echoed commands into buttons.
  2. Applying a command to multiple objects.

Trooper:

  1. Finding Parameters (Introduction to the “Object Model”).
  2. Finding How to Access Parameters Visually.
  3. Finding parameters and their values with code.
  4. Successfully using the help files.

Hardened:

  1. Creating a Plugin.
  2. Adding a Plugin to a Menu.
  3. Plugins that take Input and give Output
  4. On Event Plugins
  5. What is PyQt and how does it make my plugins AWESOME?!
  6. Installing PyQt for Softimage
  7. Creating a Simple PyQt Plugin

Veteran:

  1. Modules
  2. Multi-Model Importer

 

You can also download these lessons in PDF Book form here.

Part 4 – Successfully using the help files

2

In Xsi you get two main help files or “Guides”. If you click on the help menu in or press F1 in Xsi the “User’s Guide” will open. This can be very useful but not for these tutorials. The one we want is called the “SDK Guide” and is just under the User’s Guide in the help menu. The other way you can access the SDK Guide is by pressing F1 with the Xsi Script Editor as your active window (you can make it active by clicking on it).

In the SDK Guide you will see four tabs:

Out of the four tabs I only really ever use “Index” and occasionally “Contents”. The Contents tab is packed with tutorials and is great to use for refference and to learn a new section of Xsi. I reccomend at least taking a brief look through Contents just to see all the possibilities there are and if you have enough time I reccomend you go through the whole thing, Starting with “Script Development”, then moving to “Plug-in Development” and after that you’ll know enough to be decide what you want to do next.

The Search tab I don’t use because the Index tab is just a LOT quicker and easier to find what I’m looking for.

In a previous tutorial we used Application.ActiveSceneRoot.FindChild to find an object under the “Scene_Root”. If I type in “FindChild” into the search box under the Index tab I get quite a few options.

From here I choose “FindChild, X3DObject” but when I double click it to show the page I get a popup asking me if I want the C++ or SDK version. While using Python, VBScript or JavaScript we will always choose SDK. Then I click Display and the SDK Guide shows the FindChild Function along with the possible arguments (inputs).

So say I have 10 cubes in my scene, each one parented under another object and I don’t know any of their names. All I know is that there only one cube directly under the Scene Root and that is the one I want to get. According to the SDK Guide I can set the “Recursive” argument to False. If set to False Xsi says the FindChild function will only look directly under the Scene Root for my Object and not under any of the Scene Root’s children.

Now to only get cubes I can specify a Type. In the section that says Type, for its description there is a bit of blue text “siType” with a line underneath. Juat like web links clicking on these will take you to a different page. To find the correct argument for a cube, click the siType link.

For now we only need to use the right side of the page is displayed.

So now that we have all the arguments we need we’re ready to go:
As we can see the FindChild can take up to four arguments: “X3DObject.FindChild( [Name], [Type], [Family], [Recursive] )”
for [Name] we will use “*” – to find all names since we don’t know what the name could be.
[Type] = “cube”
[Family] doesn’t really matter for this case
and finally [Recursive] needs to be set to False.
Making our command look like:
cube_obj = Application.ActiveSceneRoot.FindChild("*", "cube", "", False)

Note that for [Family] we used “”. The “” just tells Xsi to use the default value.

Also when setting arguments, you have to set them in the correct order.
Running: cube_obj = Application.ActiveSceneRoot.FindChild("*", "cube", False)
would not have the same effect. Xsi will assume that you are setting the [Family] to False as [Family] is the option for the third argument. This is why even though we don’t want to change or set the [Family] argument, we still need to put something in there to be able to set the fourth argument correctly.

The help file may be a bit overwhelming now but most are at first and as time goes on you’ll get more comfortable with it. With this help file and google you’ll be able to acheive many marvelous things 😉

Part 3 – Finding parameters and their values with code

2

In some cases it can be easier and faster to find an object’s parameters and their values via code as opposed to having to look through the SDK Explorer (though it is very likely you will often use a combination of both).

As we already know, to gain access to a object we can do this:

sphere_obj = Application.Dictionary.GetObject("sphere", False)

From here we can either look for the parameters in the SDK Explorer or we can “loop” through them with code:

for param in sphere_obj.Parameters:
     print param.FullName

This will print the full path (FullName) to each parameter.

If we wanted the values too we could just print param.Value:

for param in sphere_obj.Parameters:
     print "Parameter:" + param.FullName
     print "Value:" + str(param.Value)

Now we can see exactly how to access each parameter and just to make sure its the right one we can check the value we see in Xsi (eg the Local X Position) compared to the value we see in the Script Editor Output (eg sphere.kine.local.posx)

The parameter’s path is made up of a combination of Objects followed by Properties and the finally the parameter’s ScriptName.

Image

In this case “sphere” is an object and “kine” and “local” are Properties.

An Object holds a collection of Properties and a Property holds a collection of Parameters.

You can loop through Properties just as you looped through Parameters with:

for prop in sphere_obj.Properties:
     print "Property:" + prop.FullName

Now that we know the Properties we can even loop through the Parameters of a specific Property:

for param in sphere_obj.Properties("visibility").Parameters:
     print "Parameter:" + param.ScriptName

From this we can narrow it down even further to:

print sphere_obj.Properties("visibility").Parameters("viewplaybackvis").Value

If there are other objects parented under the sohere and we only want to get those we can use:

for obj in sphere_obj.Children:
     print obj.FullName

which can be turned into (assuming you have an object named cone parented under your sphere_obj):

for prop in sphere_obj.Children("cone").Properties:
     print "Property:" + prop.FullName

And lastly we can call sphere_obj.NestedObjects. This returns any of the above mentioned things that are directly under the sphere_obj. I tend to use this as more of a last resort when calling Children, Properties or Parameters won’t work.

That said its about dinner time so I’m off home 😀

Part 1 – Finding Parameters (Introduction to the “Object Model”)

2

In Xsi, with scripting we have 4 different ways of getting the value of a parameter.

Say I have a sphere and I want find its X position. I can do any of the following:

print Application.GetValue("sphere.kine.global.posx")
print Application.Dictionary.GetObject("sphere.kine.local.posx").Value
print Application.ActiveSceneRoot.FindChild("sphere").Kinematics.Global.PosX.Value

You did ready correctly, I did say there are 4 ways though I’ve only shown 3. The fourth way is by using the FindObjects method. The reason I’ve left it out is it is a bit more complex to use than the others and in most cases you can get by by using one of the mentioned 3. But when you’re more comfortable coding in Xsi look it up in the SDK Guide.

The first method is by far the easiest. All you have to do to find what to put inbetween the brackets of the Application.GetValue().We can do this by changing the paramater we want in the viewport, seeing what the Script Editor Output says and changing it slightly. E.g: When I move my sphere along the X axis the Script Editor Outputs:

Application.SetValue("sphere.kine.global.posx", 3.435, "")

Now if I want to print the X Position of the sphere all I need to do is change the “SetValue” to “GetValue” and take out anything other than the sphere’s path:

Application.GetValue("sphere.kine.global.posx")

The first method is great for its ease but if I wanted to set the values of a few parameters on the sphere it would be quicker and easier to read in a script if we went with method 2 or 3. The reason for this is we can “get” the sphere and store it in a variable so we can access it whenever we want:

my_sphere = Application.Dictionary.GetObject("sphere")
or
my_sphere = Application.ActiveSceneRoot.FindChild("sphere")

The difference between method 2 and method 3 is that method 2 goes straight to the sphere object in memory and returns it. This method is by far the fastest of the three, Method 3 is the slowest because xsi does a search for an object named “sphere”. It searches under the Scene_Root, looking at the name of every object untill it finds one called “sphere”. For now we will go with the speedy method 2 but method 3 will come in quite handy later on.

So, back to the code. We now have the sphere object stored in the variable called “my_sphere”. This means we can set whatever parameter we wish quite easily. For example

Set sphere’s X position to 100:
my_sphere.Kinematics.Global.PosX.Value = 100

Set sphere’s Y Rotation to 20:
my_sphere.Kinematics.Global.RotY.Value = 20

Get the spheres Z Scale, devide it by two and store it in a variable called “half_scale” and finally set the sphere’s scale Z to out half_scale_z value:
scale_z = my_sphere.Kinematics.Global.SclZ.Value
half_scale_z = scale_z/2
my_sphere.Kinematics.Global.SclZ.Value = half_scale_z

Note: If you use Application.Dictionary.GetObject in your scene and the object you’re looking for doesn’t exist (or it you’re not sure) add a comma followed by False before your closing bracket. This tells Xsi NOT to throw an error if it can’t find the object. eg: If I want to try get an object named “cube” in my scene but I’m not sure if it exists in my scene or not I rune

my_cube = Application.Dictionary.GetObject(“cube”, False)    # This won’t throw an error if the object named “cube” doesn’t exist.

So I always write my Application.Dictionary.GetObject with a False at the end just incase 😉

Part 2 – Applying a command to multiple objects

2

We start off in pretty much the same manner as Part 1 (Easily turn echoed commands into buttons.) by opening the script editor (Alt+4) and clearing its previous output.

In this example I have a few cubes in my scene that I want to be able to hide and show with the click of a button. This is really easy to do 😉

I haven’t changed the names of the cubes, they are called “cube1”, “cube2” etc.

So now same as in Part 1 I manually do what I want and then copy the echoed commands.

So, first I select one cube. Then I press the “h” key to toggle its visibility.

The script editor echoes the commands:

Application.SelectObj("cube1", "", True)
Application.ToggleVisibility("", "", "")

This is great but if I were to put this into a button it would only toggle the visibility of “cube1”, I want to be able to do All the objects who’s names start with “cube”.

This is done with this little guy: * (an asterix or wildcard). This guy basically tells Xsi to look for anything who’s name starts with “cube”, regardless of what it ends with.

So when I run the commands

Application.SelectObj("cube*", "", True)
Application.ToggleVisibility("", "", "")

All the cubes in the scene are hidden, and when i run it again they are unhidden. When you want to edit a command, copy it from the bottom half of the script editor (“output”) to the top half (“editor”). In here you can change the commands as you please and test them too by pressing F5 or clicking the run button.

When you are happy with your code, select it and drag it onto a toolbar (you can turn code in from both the output or the editor section of the script editor, though in this case we are dragging from the editor section).

This asterix trick can be applied to many Xsi commands, here are a few more examples that would work:

To assign a material to all the cubes, after assigning manually the command in the script editor output is:

Application.AssignMaterial("Sources.Materials.DefaultLib.MyMaterial,cube1", "siLetLocalMaterialsOverlap")

so I change it to:

Application.AssignMaterial("Sources.Materials.DefaultLib.MyMaterial,cube*", "siLetLocalMaterialsOverlap")

If I want to turn off the render visibility for all the cubes, manually I get:

Application.SetValue("cube1.visibility.rendvis", False, "")

which I change to:

Application.SetValue("cube*.visibility.rendvis", False, "")



and so on 🙂