Calling Python from Substance Painter

Here is a quick code snippet for calling a Python script from Substance Painter and parsing the results.

The in/out is very simple, but serves as an example of using the alg.subprocess.check_output function to bridge the JS api and your own Python scripts.


// This can be called from the QML UI, or elsewhere in the plugin code. 
function GetAllFilesInDirectory(root)
{
  if (root == undefined)
  return;

  // I put my scripts in a relative path to keep my plugin tidy. 
  var script_path = "Scripts/FileUtils.py";

  // Gathering files
  var ret = "NOSTRING";
  try
  {
      // The arguments are used as parameter inputs to the Python script. This requires some planning
      // and well communicated conventions, but works well enough. 
      ret = alg.subprocess.check_output(
          [
          pypath, // Absolute path to interpreter.
          script_path, // Relative path to the py script.
          "log_files_of_type", // sys.argv[1], in this case the python method I'm calling.
          root, // sys.argv[2], used as a parameter for my method, in this case, the root directory to find files in.
          "spp" // sys.argv[3], used as a parameter for my method, in this case the file type extension to look for.
          ]
      );
  }
  catch(err)
  {
      alg.log.exception(err);
      return;
  }

  // Iterating the returned string 
  var file_urls = [];
  var all_files = ret.split(/\r?\n/);
  for (x = 0; x < all_files.length; x++) 
  { 
      var local_file = all_files[x];
      if(local_file.length == 0)
          continue;

      // Convert the file names to the native substance path URL format. 
      var project_url = alg.fileIO.localFileToUrl(local_file);
      file_url.push(project_url);
  }
  return file_urls;
}


The Python script is also quite simple:

function GetAllFilesInDirectory(root)
#!/usr/bin/env python3

import glob
import sys

def log_files_of_type(root_dir, ext):
    """
    Returns a list of all the files with a given extension in the named directory. 
    @param root_dir : the directory to parse. 
    @param ext : the extension to match. 
    """
    for filename in glob.iglob(root_dir + '**/*.{0}'.format(ext), recursive=True):
        print(filename)

if __name__ == "__main__":
    # Select the method to run based off of the arguments.
    # args are [0] script name, [1] method name. Subsequent args are arbitrary based on method called. 
    method_name = str(sys.argv[1])

    if method_name == "log_files_of_type":
        log_files_of_type(sys.argv[2], sys.argv[3])


I find that any script I write that passes information back and forth between two different platforms/interpreters usually comes with it's own set of headaches.

This kind of string parsing should look familiar to anyone who has done work with Photoshop bridging tools, or who has chosen to wrap the P4 command line interface themselves.

(PS... syntax highlighting borked... should fix that one of these days...)