About the project

This plugin started as a small workflow optimization for the artists in my game group; a simple button to open the selected mesh in Painter. It ended up getting a lot more features, and becoming its own separate tool. Now it not only handles sending one mesh, it also handles sending a high- and lowpoly that are automatically placed in their appropriate places inside Painter: lowpoly as the project mesh, highpoly as the baking mesh. After showing this tool to a friend, he gave me more ideas. Since I can’t automate the process of applying materials like he wanted quite yet, I had to settle on figuring out how to bake after sending a mesh to Painter. Turns out, it’s entirely possible, although not the prettiest solution, due to quite limited functionality in Painter’s scripting system. A lot of things in this tool I did because it was fun, and I wanted to see how far I could go; not because they are super useful features... Baking on import is really cool, but I doubt it is a feature that will be useful for users. The implementation of it was a lot of fun, though, which was an important part of the project. I also learned a lot while implementing it, the most important thing being that I started thinking more creatively about programming problems.

A big thank you to Jonathan Persson for the TEC-9 model he kindly allowed me to use for the presentation of this tool! The plugin is available on Substance Share if you want to try it out!


Base functionality

Substance Painter supports launching with command line arguments. These arguments can be a project mesh or whether to import that mesh with UDIMs. The base functionality of the plugin is therefore quite simple: export a FBX from Maya into a known directory, and open Painter with the command line flag “--mesh” and the directory to that mesh file. Simple enough! At this point, I had a temporary directory set up in the user’s temp-folder, just for placing the transient files somewhere. This is what the current version defaults to, but it can be changed on a project to project basis. I have also contemplated using Maya’s projects to keep track of meshes and files, although that hasn’t become a reality quite yet.

With the ability to send a single lowpoly mesh straight into Painter, how difficult could it be to send a highpoly too? Well, about that…


Writing plugins for Substance Painter

Substance Painter’s plugins are written in QML, a markup language used when creating GUIs. With limited functionality and events, I had to figure out a way to make Painter check whether a highpoly actually exist (there could be a chance that the user simply just exported a lowpoly), find the name of it, and plug it into the baking parameters. The latter is no problem, since I have access to all of the baking parameters via a baking parameters structure that holds all the current settings. Getting the name of the highpoly isn’t tricky either, as long as I assume it has exactly the same name as the lowpoly, just with a “_high” suffix instead of “_low”. This will generally not be a problem, but it could definitely be improved upon. As I will talk about later, I have a potential solution for this problem that I hadn’t thought about when I implemented this feature.

var pathToLow = alg.fileIO.urlToLocalFile(alg.project.lastImportedMeshUrl())
var pathToHigh = [pathToLow.replace("_low", "_high")]
alg.project.settings.setValue("highpoly", pathToHigh) // The path to the highpoly stored in a settings file.

The last problem was checking if the highpoly actually exist. Well, I should rephrase, it isn’t even a problem checking if it exists, there is a function for just that. No, the problem is when to do it. At the time of writing this article, Substance Painter has 12 different functions to override: onProjectOpened seemed like a logical first step. I know I don’t want to do anything at all if the user opened Painter themselves, so that’s the first thing I made sure to solve. I just check if a project is already open (which will be true if the user launched via command line) and if the title of the project is “Untitled“ (the default name for unsaved projects). This will ensure that my code will only be run when Painter is launched via command line.

onProjectOpened: {
	// Checks whether the current project is started from command line.
	if (alg.project.isOpen() && alg.project.name() == "Untitled") {
		// Store the temporary settings.
	}
}

With that out of the way, it should be as simple as checking if the file exists and plug it into the baking parameters, right? That’s where you’re wrong. For some reason, the onProjectOpened is executed before the project is loaded, resulting in an error. My solution is yet another sub-optimal hack to solve a problem that I didn’t expect would be a problem in the first place. Using the onComputationStatusChanged to set the values after the project has been created, when something changes. I simply get the names in onProjectOpened, create a temporary boolean that is stored in a settings file in the plugin directory, and read that file in onComputationStatusChanged. If the bool exists and is true, set the baking parameters, if not, remove the key if it exists and go on with the rest of the texturing business.

// Create the parameters that contain the highpoly meshes.
var params = alg.baking.commonBakingParameters()
params.detailParameters.High_Definition_Meshes = alg.project.settings.value("highpoly") // Get the path to the highpoly.
alg.baking.setCommonBakingParameters(params)

Final thoughts

I really enjoyed working on this project. As I said in the introduction, this tool allowed me to think differently when programming; thinking creatively when there are no obvious solutions. I know I would’ve loved to use this tool as an artist, because getting the mesh into Painter and iterating on it was the most annoying part of working with the two programs. I have gotten great feedback from the artists that have used it.

I enhanced this tool further in the game projects, to reduce human error as much as possible. In that customized version, I also set what export preset should be used, one with names based on either texture set or the mesh name, depending on what type of export it detects. For example, a high and lowpoly will have “wrong” mesh names, since they include the “low” and ”_high”, but their texture set name is correct. The opposite is true when no highpoly is detected, since Maya can’t have a material have the exact same name as a mesh. Additionally, I also set the export path and file type immediately, so the DDS Exporter works with no setup necessary at all! I believe this tool has saved an immense amount of time for our artists.