I present you my first plugin for Adobe Substance 3D Designer: “Print Modified Values”. It’s going to be a long post, so I’m going to divide it into several sections:
- How to install it
- How to use it
- Some comments and known limitations
- Brief production diary – Problems that have arisen and how they have been solved
The purpose of this plugin is to add a Comment, as a “child” of a node, containing the information of those parameters that have been modified.
In February 2023 I will teach a Designer course in my town (I will share more information soon) and in the preparation of the contents for that course I immediately realized how useful this tool would be. I searched and asked around, but found nothing, so I got down to work myself.
After a lot of work, here you have the first version. I hope it will be useful, especially to those of you who like to spread the word by sharing screenshots of your Graphs. It is certainly saving me a lot of work, hopefully more than it has taken me to create it… 🤪 In any case I have learned a lot, not only about Designer API, but also about the internal structure of the application itself.
SHORT: I have done my best to create a stable and robust tool. But use with caution, please. I recommend to create a copy of your graph before proceed to comment your nodes using this tool.
LONG: THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
How to install it
Once you have downloaded and unzipped the ZIP file, move the main folder “print_modified_values” (which also contains a .json file and another subfolder folder with the same name) to where you normally have other Designer plugins installed.
If you have never done it I recommend you to use this directory:
/Users/NAME/Documents/Adobe/Adobe Substance 3D Designer/python/sduserplugins/
Then start Designer, open Preferences / Projects, click on the vertical tab “User project” (inside Configuration / Project Files) and then on the horizontal tab Python. Click on the little + icon on the right and add the above directory. And as indicated in yellow, once this is done you will have to restart Designer.
Once Designer is restarted, the plugin should be loaded. But just in case, open Tools / Plugin Manager and check that the checkbox of our plugin is enabled. Doing this, by the way, you can activate or deactivate any plugin.
As soon as you install it, you will not see it appear in your top bar. To do this you must first create a new Substance Graph. Do it. Now you will see 2 new little buttons appear in the toolbar: the one without color can activate or deactivate the presence of the other one, in orange.
How to use it
It is very simple: suppose we have a Splatter Circular node that we have modified and we want to write down what parameters we have changed. We simply select it and click the little orange button. Automatically a Comment node will be added as a “child” of our Splatter (“child” means that when we move the node, the comment is moved with it).
You can also use “Q” as a keyboard shortcut instead of the button, as it is much more agile. I have chosen that letter because it evokes “Query” (ask the node about its own parameters). If you don’t like this shortcut or it conflicts with another one you already have, you can change it by editing the __init__.py file located inside the “print_modified_values” subfolder. But be careful not to touch anything else, if you don’t know what you are doing 😉.
Some comments and known limitations
One at a time
You can only add comments one at a time, do not select several nodes at once. It would not be very difficult to change the code to make it comment several nodes at a time, but for the moment I prefer to keep it this way, for several reasons. Among others, because if it gets stuck with one node, it would stop the whole process. I have also found that as we add comments we have to move the nodes to make room for others so that they do not overlap, so it is convenient to go one by one. In addition, when selecting several nodes, if we increase too much the number of them, we could start to “overload” the program.
“All by default”
When a node has not been modified, a Comment indicating “All by default” is also added. Even if I end up deleting it later, in principle I prefer it to work this way, because that way I am more sure that it has been processed correctly. If I didn’t add any Comment, I couldn’t be sure if it’s because it’s all by default or because there has been some internal error. When the plugin encounters a problem, it stops, giving an error and doesn’t add anything. That can only be seen if we open the console. I’ve also found that I’d rather see an “All by default” than see nothing, because otherwise I wouldn’t be sure if maybe I forgot to comment out that node…
Why does it sometimes do nothing?
In some nodes (SVG, Bitmap, FX-Map, Inputs… and likely some other) you will get a “Non supported” comment or maybe it does nothing. Actually it does, and what happens is that it gives us an error, as you can see in the console. There are many possible types of data structures. At the moment it covers all those that are within these groups: Enumerators, Arrays, Int, Int2, Float, Float2, Float3, Float4, ColorRGBA, Bool, String and Texture. But I am sure that I have left out some typology…
For example, when the API gives us the info of a certain Position Random value, it does it this way:
('Position Random', 'SDValueFloat2(float2(0.17365,0.3249))')
And the plugin takes care of converting this to a much more readable and rounded one:
Position Random 0.17 0.32
And it is very likely that for a certain Property “x” of a certain Node “y” a data type is used that I have not covered in my code. Or maybe the error is due to some other reason, you never know…
Anyway, after using it for a few days in many graphs, I can tell you that it works great 99% of the time and has not caused me a single crash or application destabilization problem.
The shortcut “Q” no longer works
What I have seen very occasionally, is that suddenly the keyboard shortcut stops working (although the button still works). As I say it is very occasionally, and I have not managed to find out why it happens, a very rare thing. One way to get it back without restarting the application is to open the Plugin Manager, deactivate and re-activate the plugin. And then select or create a new Graph for it to load again. Then the keyboard shortcut will work again.
Brief production diary
The creation of this plugin has been a small odyssey. To begin with, I’m not a programmer, I don’t even consider myself a real hobbyist. My only experience with code has been creating some Python scripts for Modo and it’s been quite a few years… So from the beginning I ran into a lot of problems, mostly due to my total inexperience with the Designer API, but also some due to certain limitations of it and of course to my poor experience with Python itself (I can’t get to grips with the concepts of “Classes” and the constant use of “Self”).
In case any of you are interested in the more technical side of these matters, here is a sort of “production diary”.
I intended to query the selected node to find out what its default parameters were and put them in a dictionary. Then find out the current parameters -some modified and some not-, create a second dictionary, to finish comparing both dictionaries and see where the differences were (that is, only the modified parameters). Looking in the Designer API documentation I see that there is a getDefaultValue() so I try to use it, but no matter how many times I try, I can’t get it to give me those default values. Either I get error messages or “none” (!?) results.
So I go to the Substance Designer Discord where I know there is a good atmosphere, support and you can even chat with Adobe staff. After a few laps, an Adobe employee, alias est, confirms what I feared:
“Looking at our code, it seems default values for properties are not correctly implemented for some types of graphs. I created a ticket in our issue tracker to fix this in the future. A not very nice alternative for now could be to create a new node, get the property values and then delete the node”.
Oh my goodness, falling at the first hurdle! It’s bad luck, my first plugin and we start with problems….
So to continue, for the moment I decide to add a second “virgin” node to use it as a reference. Logically the goal is to do it in the code itself, but first let’s see if it works by adding it by hand.
The idea now is to select first the virgin node, then the modified one and create the two dictionaries and then compare them. I am making progress, but soon I realize that sometimes I get the information from the “wrong node”. The problem lies in a new limitation of the program that I already guessed and the good est confirms me again:
“Selections in Designer are not ordered. There are no guarantees about the order of selected items.”
That is, even if I select first the virgin node and then the modified one, Designer does not retain that information. It simply creates an array with the selected nodes, but without knowing in which order. So there is no way to be sure which is the virgin and which is the modified.
This is a bummer, but I’m still moving forward, because when I learn how to add the reference node automatically I will be able to solve it, I want to believe. Thanks to the help of another Adobe employee, Luca Giarrizzo, I manage to turn what until then was just basic code pasted into the Python Editor of Designer into a real plugin.
Once I have the plugin assembled and I manage to solve some other things (like the correct ordering of the parameters, so that they appear the same as in the node) I focus on trying to add the reference node internally, through code. And again I run into an obstacle.
I quickly learn that adding an Atomic Node is as simple as:
sdSBSCompGraph.newNode('sbs::compositing::blend') — It works.
However, if I try to do the same with an Instance Node:
sdSBSCompGraph.newNode('sbs::compositing::shape') — It doesn’t works.
Once again, good old est comes to the rescue to confirm that it’s not that simple:
“Content from the library or graphs in the explorer that you want to instanciate into a graph are not nodes, they are node instances (of a graph). The process to instance a graph into another graph is a bit convoluted today. Basically you need to open the package that contains the graph you want to instance, get an SDPackage, find the SDGraph that you want to instance and call SDGraph.newInstanceNode(sdGraphToInstance). We would like to make this process easier in a future version of Designer.”
After thinking about it and watching some tutorial I manage to find out how to add an Instance Node. For example, for a Shape it would be:
resource_dir = sd_application.getPath(SDApplicationPath.DefaultResourcesDir)
file_path = os.path.join(resource_dir, 'packages', 'pattern_shape.sbs')
package = sd_pkg_mgr.loadUserPackage(file_path)
resource = package.findResourceFromUrl('shape')
Great! I know how to add any type of node.
But, wait a minute, there is still an important question: I only know how to add a node if I know beforehand whether it is Atomic or Instance, and also if I have two pieces of information: its Package and its Graph Instance.
So the first thing is to find out all the information of the (modified) node that we have selected, to be able to add another one like it, but virgin. I dedicate a considerable amount of time to the subject… and I don’t manage to find it out. I ask it again in the Discord channel… twice… and I don’t get any answer.
I spend a couple of days studying the video tutorial “Intro to Plugin Creation in Substance Designer” by one of the biggest Genius in Designer, Ben Wilson, and searching for information on my own… but finally I can’t figure it out.
In addition, I also detect a new problem: when I add an Instance Node with the code I have pasted above (learned from Ben’s tutorial) I detect that apart from adding the node to my current Graph it also opens the original Package of the node in the Explorer. Which means that I will also have to learn how to close it (which seems obvious but I can’t find it how).
And there are still more problems, not so serious, but I don’t want to drag this out too long. So, tired as I am, I give up. I can’t dedicate any more time to this…
As I had already shown on Twitter some screenshots of the plugin in action, even with that limitation of having to add an extra virgin node to use it as a reference, I decide to share it as I have it. And I even go as far as writing this post explaining all the problems and asking for help to try to improve it. That was on Sunday afternoon, November 13.
And suddenly there was light!
But Monday arrives, and when I had already decided to share it that way, as I say, suddenly a guy named Divyansh, another Discord user, answers me and gives me a good clue of how to continue. Finally I learn how to obtain the Package and the Graph Instance of the selected node.
Soon, already by private, the good Luca Giarrizzo also answers all my doubts, and little by little I see how to solve the problems: to know if the node I have selected is Atomic or Instance; to know how to continue operating with the two nodes, the original modified and the virgin reference, without having to select them; I discover myself how to delete the package that opens automatically and how to delete the reference node itself once its dictionary has been obtained…
Little by little I am polishing many things, especially how to get that instead of receiving an integer “3” when we have selected a certain parameter from a drop menu, to extract the information that this really corresponds to a “Multiply” mode, thanks to the use of Enumerators, to deal with certain inconsistencies in the assignment of integers in those same Enumerators, to know how to create dictionaries on the fly, “live”, interrogating different Properties of the node, to adapt the information we get from Levels if it acts in Grayscale or Color mode, etc., etc.
So finally, after a lot of work and detours (you don’t want to know the amount of hours and days I’ve spent on this…) I managed to make the plugin work exactly as I wanted it to. It only remains to test it for a few more days, preparing those training materials I was talking about at the beginning, and finally share it with the rest of users. Because I don’t feel like keeping something like this for myself, nor do I want to ask for money for it. There are many generous people who altruistically share a lot of useful information and resources, without asking for anything in return. And that’s something I like to participate in.
So finally I hope you too will find it as useful as I find it 🙂