Multi-Model Importer

1

Hi There

Something that can get a bit annoying is only being able to import one model into Xsi at a time. Yeah we can drag a whole bunch of models in from explorer but if we create our own Multi-Model Importer plugin, firstly we’ll be rid of the mentioned annoyance and secondly we can add a whole lot of functionality to out import plugin, like where to look for the models, or what to call them and in this example inorder to keep the scene nice and neat I’m even going to get this plugin to place each model type in a specific category.

My categories will be:

  • Characters
  • Lights
  • Cameras
  • Props

And to keep this from getting complicated, while I’m here I’m just going to make those models. An because this is a coding blog I’ll make and export the models with code 😉

import os
from random import randint
xsi = Application

# This Xsi Project's folder path
project_path    = xsi.ActiveProject.Path

def Randomize_Position(obj):
    # Get random numbers between -10 and 10 and set as position
    obj.Kinematics.Local.posx.Value    = randint(-10,10)
    obj.Kinematics.Local.posy.Value    = randint(-10,10)
    obj.Kinematics.Local.posz.Value    = randint(-10,10)

# Create Cube called "Cube_Character"
cube_obj    = xsi.CreatePrim("Cube", "MeshSurface", "Cube_Character")
Randomize_Position(cube_obj)    
model_path    = os.path.join(project_path, "Models", "Cube_Character.emdl")
xsi.ExportModel(cube_obj, model_path)

# Create Sphere called "Sphere_Character"
sphere_obj    = xsi.CreatePrim("Sphere", "MeshSurface", "Sphere_Character")
Randomize_Position(sphere_obj)
model_path    = os.path.join(project_path, "Models", "Sphere_Character.emdl")
xsi.ExportModel(sphere_obj, model_path)

# Create Spot Light called "Spot_Light"
spot_obj    = xsi.GetPrimLight("Spot.Preset", "Spot_Light")
Randomize_Position(spot_obj)
model_path    = os.path.join(project_path, "Models", "Spot_Light.emdl")
xsi.ExportModel(spot_obj, model_path)

# Create a Camera called "Main_Camera"
cam_obj        = xsi.GetPrimCamera("Camera", "Main_Camera")
cam_obj.Kinematics.Local.posz.Value = 50
model_path    = os.path.join(project_path, "Models", "Main_Camera.emdl")
xsi.ExportModel(cam_obj, model_path)

# Create a Cylinder called "Cylinder_Prop"
cyl_obj        = xsi.CreatePrim("Cylinder", "MeshSurface", "Cylinder_Prop")
Randomize_Position(cyl_obj)
model_path    = os.path.join(project_path, "Models", "Cylinder_Prop.emdl")
xsi.ExportModel(cyl_obj, model_path)

Note: os.path.join below adds the slashes between folder names depending on the opperating system you’re on. So for windows it would put “\\” between the project_path, “Models”, and “Cylinder_Prop.emdl” and for mac or linux, “/” would get put between them. The using os.path makes your plugins a lot easier to take across multpile Opperating Systems.

Now that thats done you should have all those objects exported to your Models folder

And we can continue with our plugin:

So as with all the previous plugins, you need to start by creating it 😛

File >> Plug-in Manager >> File >> New >> Command.

Set the Command Name to: Multi_Importer and in the “Command Definition” tab, Add an Argument (by clicking “Add”).

Change the name to “default_folder”. Then click “Generate Code”.

The reason we added that “default_folder” option is that if we want to call this command from another plugin, we can tel it where to look for models to import. When you’re working with loads of models in loads of folders it can save quite a bit of time if your tools just “jump” to the correct folders instead of the users having to browse manually each and everytime they want to load or reload a model.

So lets setup this plugin to do some PyQt magic. At the top:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

And in the “Multi_Importer_Execute” we need to get the Qt Softimage Anchor:

    import sip
    sianchor = Application.getQtSoftimageAnchor()
    sianchor = sip.wrapinstance(long(sianchor), QWidget)

But now instead of creating our own custom QDialog like we did with the AnimationCopy Plugin we’re going to use the existing QFileDialog. You can find out more about it here. And if we look around a bit zyou go further down it’s even got the perfect function for us here.

QStringList QFileDialog.getOpenFileNames (QWidget parent = None, QString caption = QString(), QString directory = QString(), QString filter = QString(), Options options = 0)

Now all we need to do is set that up. The QStringList tells us that this function returns a QString list, and the rest tells us what we need to input.

    modelPath_lst    = QFileDialog.getOpenFileNames(parent = sianchor, caption = "Import Models", directory = default_folder, filter = "*.emdl")

The parent we set to be the sianchor (which we must always do), the filter we set to look for anything that ends in “.emdl”. Remember, the * tells Xsi and in this case PyQt to look for anything and everything, so when the QFileDialog is launched, we will only be able to see files ending in “.emdl” and folders so we can navigate to the desired files if need be. The directory is set to our default_folder variable but currently the default_folder isn’t set toi anything so if we run the plugin without inputting a default_folder, its not going to take us anywhere useful.

To make it default to your Xsi project’s Models folder, at the top of your plugin import the os module.

Then after the bit that says:

null = None
false = 0
true = 1

Add a variable named “models_folder” and set it to this project’s path:

models_folder    = os.path.join(Application.ActiveProject.Path, "Models")

And in the Multi_Importer_Init  function, change the line:

    oArgs.Add("default_folder",constants.siArgumentInput,"None")

to:

    oArgs.Add("default_folder",constants.siArgumentInput, models_folder)

Now the default_folder variable is set to this Xsi project’s Models folder by default.

So when you save and run your plugin, it should popup something looking like this:

To get the models the user selected after they press the “Open” button we loop through the modelPath_lst just like we do with standard python lists. From there we can import each model but remember, the QFileDialog returns QObjects, so the QStringList it returned will be full of QStrings. If you give Xsi a QString it won’t know what to do with it so turn the paths into standard python strings first:

    for mdl_path in modelPath_lst:
        mdl_obj    = Application.SIImportModel( str(mdl_path) )[1]

Now you have a Multi-Selection Model Importer :D.

For those of you that have a programming background and know what threads are, unfortunately you cannot run Xsi’s ImportModel function in anything other than the main thread. If you don’t know what I’m talking about don’t worry, you’ll come across Threading sooner or later 😉

Next thing to do is add the imported models to their relevant categories.

So firstly, we need to determine the category of each imported model. To do this I’m going to create a function near the top of our plugin:

After:

null             = None
false             = 0
true             = 1
models_folder    = os.path.join(Application.ActiveProject.Path, "Models")

Add:

def Get_Cat(mdl_obj):
    mdl_cat    = None
    # Look at each category name in the cat_lst
    for cat in cat_lst:
        # If the category name is in the model's name
        if cat in mdl_obj.Name:
            # Then set the mdl_cat that will be returned to this category
            mdl_cat    = cat
            # Stop the loop here. There's no point looking further if the category has already been found
            break
    return mdl_cat

And then in the loop that imports the models, after the line:

        mdl_obj	= Application.SIImportModel( str(mdl_path) )[1]

Add

        mdl_cat	= Get_Cat(mdl_obj)

This plugin may be used to import models that aren’t in any of the categories though so rather than limit it to only being able to import models with a category, if the model has no category “None” is returned for the mdl_cat. So when this happens we can just LogMessage that the specific model has no category:

        if mdl_cat    == None:
            Application.LogMessage("Multi_Importer: the model " + mdl_obj.Name + " doesn't have a valid category.", constants.siWarning)

Otherwise if the category is valid we:

  1. Look for that category null.
  2. If the null doesn’t exist, create it.
  3. Parent the model under the null.

And the code for that is:

        else:
            cat_nll    = Application.ActiveSceneRoot.FindChild(mdl_cat, constants.siNullPrimType, "", False)
            if not cat_nll:
                cat_nll    = Application.GetPrim("Null", mdl_cat)
            Application.ParentObj(cat_nll, mdl_obj)

Above, we look under the Active Scene Root for an null named whatever is stored in the mdl_cat variable. Note the False at the end. This tells Xsi to only look directly under the Scene_Root and not within objects under the Scene_Root incase a Character has a null in it named Character in it’s rig setup or for any other reason.

And after running the tool, if you select all the available models for import you should get something like this in your scene:

Note: If your Camera null is named Camera1 and the same for your light null, it is because there is already a object named camera in your scene and another object named light. Xsi will automatically add a number to the end of an objects name if that name already exists elsewhere because no two objects may have the same name (unless they are under Models). So just delete the camera and light before running the pugin.

And finally:

When you want to export and object from Xsi it has to be in a model. If it isn’t already Xsi will create and put the object you want to export in one. You could export Obj’s but those won’t keep all the information as models do. Models store everything that was applied to that object in the Xsi scene so when you import it again it will have the same animation, constraints, settings and everything else it had in the original scene. This is why exporting objects as models is in most cases the way to go.

The thing is when you import a model you may not necessarily want to keep the actual model but only want to keep the object inside. For instance, a Character or Prop would generally be rigged so you’d want that to stay in a model when in Xsi but a Light or Camera may have no reason what so ever to be kept in a model.

So for the Light and Camera Categories we’ll set the plugin to remove the objects from the model:

Within the loop that imports the models, after the line where you set the model’s parent to be the relevant category null:

            if mdl_cat in ["Light", "Camera"]:
                for child_obj in mdl_obj.Children:
                    Application.ParentObj(cat_nll, child_obj)
                Application.DeleteObj(mdl_obj)

This checks if this model’s category is a Light or Camera. If so it finds all the objects directly under the model and parents them directly under the category null (removing them from the model). Lastly we delete the empty model object.

 

And that’s that 😉

When you run the plugin on an empty scene the expolrer should look like:

 

# Multi_ImporterPlugin
# Initial code generated by Softimage SDK Wizard
# Executed Thu Oct 11 19:02:20 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
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os

null             = None
false             = 0
true             = 1
# Path to this project's Models folder
models_folder    = os.path.join(Application.ActiveProject.Path, "Models")
# List of categories to be created
cat_lst            = ["Character", "Light", "Camera", "Prop"]

def Get_Cat(mdl_obj):
    mdl_cat    = None
    # Loop through categories and check if the category is in the model's name
    for cat in cat_lst:
        if cat in mdl_obj.Name:
            mdl_cat    = cat
            break
    return mdl_cat    
        
def XSILoadPlugin( in_reg ):
    in_reg.Author = "jared.glass"
    in_reg.Name = "Multi_ImporterPlugin"
    in_reg.Major = 1
    in_reg.Minor = 0

    in_reg.RegisterCommand("Multi_Importer","Multi_Importer")
    #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 Multi_Importer_Init( in_ctxt ):
    oCmd = in_ctxt.Source
    oCmd.Description = ""
    oCmd.ReturnValue = true

    oArgs = oCmd.Arguments
    # The default_folder is set to the models folder incase no default_folder is specified
    oArgs.Add("default_folder",constants.siArgumentInput, models_folder)
    return true

def Multi_Importer_Execute( default_folder ):

    Application.LogMessage("Multi_Importer_Execute called",constants.siVerbose)
    
    import sip
    sianchor = Application.getQtSoftimageAnchor()
    sianchor = sip.wrapinstance(long(sianchor), QWidget)
    modelPath_lst    = QFileDialog.getOpenFileNames(parent = sianchor, caption = "Import Models", directory = default_folder, filter = "*.emdl")
    
    # Loop through file paths of the selected models
    for mdl_path in modelPath_lst:
        mdl_obj    = Application.SIImportModel( str(mdl_path) )[1]
        mdl_cat    = Get_Cat(mdl_obj)
        if mdl_cat    == None:
            Application.LogMessage("Multi_Importer: the model " + mdl_obj.Name + " doesn't have a valid category.", constants.siWarning)
        else:
            # Try find existing Category Null, if there isn't one then create it.
            cat_nll    = Application.ActiveSceneRoot.FindChild(mdl_cat, constants.siNullPrimType, "", False)
            if not cat_nll:
                cat_nll    = Application.GetPrim("Null", mdl_cat)
            # Put the model under the specific category null
            Application.ParentObj(cat_nll, mdl_obj)
            
            # Check if this model's category is light or camera
            if mdl_cat in ["Light", "Camera"]:
                # Get all the objects inside the model (in our case there is only one but there may be more)
                for child_obj in mdl_obj.Children:
                    # Put each object that is inside the model under the category null
                    Application.ParentObj(cat_nll, child_obj)
                # Delete the mepty model
                Application.DeleteObj(mdl_obj)

        
    return true