Houdini: Point at center of primitive VEX

I didn't come up with this, but it's so handy I'm just gonna put it here (credit to this odforce post)

This this in a per-primitive wrangle to get a point at the center of each prim.

// Adds a point at the position of each prim
addpoint(0, @P);
// Remove primitive and all points connected to it
removeprim(0, @primnum, 1);

RenderDoc and Steam- Capturing Steam Apps in RenderDoc

Quick tip- if you want to use RenderDoc to capture an app you bought on Steam, don't launch the app directly via RenderDoc (for example by using the exe in the steamapps/common dir).

You want to wrap the steam exe instead and capture child processes.

To do this:

  • First kill any existing Steam processes.
  • Launch "C:\Program Files (x86)\Steam\Steam.exe" with "Capture Child Processes" checked.
  • You should now be able to launch any DX* apps and get frame captures from them. They will show up in RenderDoc as child processes. 

ZBrush Bake Helpers

I made a thing to help make my ZBrush bake workflow faster and less frustrating. I've put it up for the price of a coffee, which I now have the time to get due to all the time it saves me.

Maya Game Exporter Hax

I've been having some fun using the GameExporter that comes bundled with Maya. It's really nice to have a well featured exporter that you don't have to write yourself.

There is one thing that really bugs me about it though- I just can't find the button to suppress the "Replace files that already exist?" and "Success!" messages. Sure, I like the validation, but when I need to export 100+ files through a batch process it can get a bit tedious.

It doesn't feel as rewarding the 80th time...

Sooo... rather than doing anything fancy like finding the topmost window and deleting it or whatever, what if I just made it so the GameExporter just, you know, wouldn't do that, and just log to the script editor all nice like.

Enter global proc redefinition! Probably very familiar to anyone who uses MEL more than I do, you can redefine a gobal proc at any time, stomping it's previous behavior. (it's a bit more nuanced than that, but for my purposes, STOMP STOMP GOOD)

Anyway, you can probably see where this is going. I don't like the dialogue windows, and I would prefer them to just print to the log. Deep inside <InstallDir>\Maya2018\scripts\others are a bunch of scripts tellingly named "gameFbxExporter........mel".

Taking a browse through these I found three procedures that I happily made my own versions of:

global proc int gameExp_OverwriteExistingFile(string $path)
{
    print("Force overwriting files.\n");
    return 1;
}


global proc int gameExp_OverwriteExistingFiles(string $fileNameList[], int $overwriteListLimit)
{
    print("Force overwriting files.\n");
    return 1;
}

global proc gameExp_ShowMessage(string $message, int $msgType)
{
    print $message;
    print "\n";
}


Executing that through the script editor now logs my super helpful messages, but without the messy dialogue prompts.

Now, you might be thinking, it would be less destructive to have taken the whole original function and maybe, you know, added an option for suppressing the dialogue, and you would be right.

But STOMP STOMP GOOD. BATCH EXPORT GOOD. Also tired.

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...)


Making A Hello World Substance Painter Plugin



Substance Painter plugins use the QT Meta Language, or QML files to build their interface. From Wikipedia:

It is a JSON-like declarative language for designing user interface–centric applications. Inline JavaScript code handles imperative aspects. It is part of Qt Quick, the UI creation kit developed by Nokia within the Qt framework.

Using this information, we can start building a window with a button to execute our script.

Visual Studio Code


For this tutorial I’m going to be using Visual Studio Code, a lightweight IDE from Microsoft. It’s not the same thing as Visual Studio, but it’s a nice scripting editor that’s available on Mac, Linux and PC. Other text editors like Sublime will do the job quite well.

Installing QML Syntax Highlighting


  • By default, QML files will appear as ordinary text files in Visual Studio Code. While this won’t stop you from being able to write the plugin, adding some QML support will make reading, organizing and editing our script easier. 
  • To do this, I’m going to install a QML extension in Visual Studio Code for Syntax highlighting. 
  • Use the View->Command Palette and type in Extensions: Install Extensions







  • Type in “QML” into the search bar and install the “QML language support for Visual Studio Code” extension. 
  • Once it is installed, restart visual studio code- your QML files will now have syntax highlighting. 

Starting our Plugin

  • This is some documentation online for substance painter scripting, but your installation also comes with some example plugins.
  • Plugins for Substance Painter live in 
    • Windows : C:\Users\*username*\Documents\Allegorithmic\Substance Painter\plugins 
    • Mac OS : /Users/<username>/Documents/Allegorithmic/Substance Painter/plugins 
    • Linux : /home/*username*/Documents/Allegorithmic/Substance Painter/plugins 
  • Note that for this tutorial I am using Substance Painter 2017x - the plugin directory changed since version 2.
  • This is where we will be creating our plugin- be sure to look through the other plugins there to see how they work. 
  • Valid plugins here will automatically be detected by Substance Painter when it is first opened, and become available via the Plugins menu option. 

Making a Hello World Plugin

  • For a plugin to be valid, it needs to have a definition, and a main entry point qml file. 
  • Navigate to the plugins directory for your system. 
  • Create a folder here called “HelloPlugin” 
  • Inside the plugin, create two files- 
    • plugin.json 
    • main.qml 

Filling in the main file:

  • The main.qml file is the entry point to your plugin. 
  • When the plugin is first loaded, this file will be used to initialize any additional data or properties the plugin needs, like adding extensions to the main toolbar. 
  • For now, we are going to make a really simple main function, which will log “Hello world!” to the console when the plugin is loaded. 
Ship it!

Filling in the JSON file:

  • The JSON file contains a manifest of metadata about your plugin. 
  • When you use a plugin’s “About” menu in Substance painter, the data you see there is populated from the plugin.json file of the relevant plugin. 
  • For example, the resources-updater plugin ‘about’ window looks like this: 

  • And looking at the plugin.json file of the resources-updater plugin, we can see how this data is defined: 



  • In this case, our JSON file is defining key : value pairs which are read by Substance Painter when it loads the plugin. 
  • We can use the same structure in our HelloPlugin to have a simple about window available. 



What we get so far:

When we load the plugin...


Our very informative "about" window. 
  • Right now we can see the plugin load, but it’s not particularly useful. 
  • We also get an about window courtesy of the plugin.json file. 
  • Let’s add a window- later we can use this window to add buttons and other functionality. 

Adding a window to the plugin

  • Create a new file in our HelloPlugin directory. 
  • Name it HelloWorldWindow.qml
  • This is going to where we define our window object.
  • Inside the file, we are going to add just enough code to define an extremely simple window. 
  • A few things to note: 
    • The window class is imported using the import AlgWidgets 1.0 call. 
    • The properties are like variables on the object type.
    • Assigning a specific id to an object is useful, as it allows us to reference this object elsewhere in our plugin. 
    • Likewise, properties like the visibility can be accessed via the object id.  
  • We now have enough code to build a simple window, but before we can see our window, we need to instantiate it in the main.qml file. 
  • Open the main.qml file, and at the top, add the following code: 



  • The code above creates an instance of a HelloWorldWindow and assigns it the id “window”. 
  • As the window is first instantiated when the plugin is loaded, in order to see it you will need to disable and re-enable the plugin. We will fix this later. 
Succinct. 

Creating a button:

  • Rather than logging to the console when the plugin is loaded, let’s make a button to do that. 
  • Open the “HelloWorldWindow.qml” file. 
  • We are going to add three things- 
    • A series of layout elements. 
    • A label. 
    • A button. 

  • QML windows are created using a series of nested layout objects- for our purposes we are going to use a column, a rectangle and a row. 
  • We need to add additional import statements to access these object types- QtQuick and QtQuick.Layouts.
  • The column represents the overall layout- elements will be stacked within this shape in the order that they are added. 
  • The rectangle allows us to fill a partition of this column with a child layout. 
  • Finally the row layout allows us to add elements that will be rendered from left to right in the order in which they were added. 
  • Adding an AlgLabel and AlgButton in the row layout adds two new elements to our window. 
  • Finally defining the “onClicked” event for the button replaces where we were logging in main.qml on startup. 


  • I also commented out our log in the main.qml file. 

Our button is very chatty. 


Reloading your script:

  • At this point it’s good to know you can reload your script on the fly using the Plugins->HelloPlugin->Reload menu in painter. This is going to be super useful as we add more complexity. 

Adding our plugin to our toolbar:

  • Right now if you close the plugin window, it’s gone until you restart the plugin. 
  • To solve this issue, let’s add a button to the toolbar. 
  • The button in the toolbar is going to be pretty simple- all it’s going to do is toggle the visibility of our plugin window when it is pressed. 
  • First, we are going to make our HelloPluginWindow start invisible. 
  • We do this by changing the ‘visible’ property from ‘true’ to ‘false’ in the HelloPluginWindow.qml file. 
  • Now we are going to make a new file called ‘toolbar.qml’ 
  • It’s pretty simple, and similar to what we have done before- it’s just a row, with a button. 
  • Something important- the property variable “windowReference” will be filled in by our plugin when it is loaded. 
  • Because locally the windowReference starts as null, we wrap the calls to it later inside a try/catch block. 
  • This will stop terrible things from happening, like a crash, if something elsewhere in our script stops us from being able to make the window- instead of a crash we can log information to the console. 

catch(err), catch(err) not a belly scratch(err)...
  • Finally, in our “main.qml” file, we are going to add an new toolbar widget, which will instantiate our “toolbar.qml” as a button on the toolbar. 
  • We also assign our HelloWorldWindow instance to the windowReference variable in our toolbar button, using it’s id. 




  • The end result is a giant blot on the toolbar that we can click to turn our window on and off: 
Now thats UI...


Adding an icon

  • Finally, even with as good as our giant white square looks, we can add an icon.svg file to our plugin and add it to the toolbar.qml script to make it more appealing. 
  • This icon will be used in the substance toolbar to show us which button is linked to our window. 
  • Wikimedia commons has some good free svg files. For our purposes I am going to use this one
  • To add the icon to our toolbar, we need to add an Image block. 
  • This is what we are going to end up with: 

Hi! What a friendly little widget.
  • To make the icon to render correctly, we need to assign it a rectangle area, and make an image widget which is the child of that area. 
  • Using the Rectangle area gives us control over the hover state colors, and the anchors. 
  • The Rectangle is a child of the Button widget, and will inherit the button’s size information by using the “anchors.fill: parent” hint. 
  • We make sure the image also inherits these size settings by using the same hint in the child Image widget. 
  • This is the code we end up with: 


  • Beside giving us a nice little icon, there is something interesting going on where we define the color of the rectangle based off the hover state. 
This is called a ternary operator, defined by the ? symbol. 

  • In this instance the rect.hovered boolean allows us to change the assignment of a variable based on the state of a boolean. 
  • The hovered state can also let us add some other cool things, like animations, to our widgets. 

Making our icon animate:

  • As a fun little exercise, we are going to animate the hand using qml animation sequences
  • Let’s make the hand wave at us. 




  • Using this code, our hand will wave at us twice each time we hover over it. 

Hellloooooo!

  • Note that the sequence is actually made out of a number of nested animations. These will run top to bottom, and in the cast of the nested sequence, each animation nested in that sequence will play before moving to the next animation in the parent sequence. 
  • Using this technique, you are able to add some complex behaviors to your widgets. 

So that's it for adding a basic plugin to Substance Painter! I'm going to follow this up with another post where we actually make it do something useful...

-Pete

Egad

One line Unreal Engine 4 (mac os) Review:
Doing Ue4 development on a Mac is not a great experience.

Update:
https://issues.unrealengine.com/issue/UE-23624 Just ran into this one... cleaning your project in XCode nukes the actual editor. Case in point.