Skip to content

Conversation

@ld-kerley
Copy link
Contributor

Overview

This proposal aims to define the specification language that would be added to the MaterialX specification to describe the new template syntax that was introduced in this PR (#2451).

Details

The templating PR describes and implements a system intended to simplify the authoring of the MaterialX standard data library, making it less repetitive, and consequently less error-prone. The system introduces new syntax to the XML-based MaterialX grammar to provide the ability to describe families of nodes in a single "templated" definition, reducing the need to explicitly state each different node signature that exists for a given node.

The PR above explicitly intends for this new grammar to be introduced in the standard data library source files, and concretely NOT in any MaterialX data library files that are built or installed by the CMake-based build process. This approach has the advantage of not requiring that any downstream integrations of MaterialX have knowledge of this update to the grammar, as all templates can be expanded at build time. This is a deliberate design choice of the PR, as discussed in the previous proposal and associated TSC meeting discussions, as it will allow the project the freedom to iterate on the grammar without affecting downstream MaterialX integrations. As such, the original PR did NOT intend the formal MaterialX specification to be updated initially, as the generated data library files should be unchanged by this process. We are initially just introducing an optimization of the sources used to generate the data library during the build.

Once the PR has been merged, the new build-time template grammar has been given time to mature, and feedback gathered from MaterialX stakeholders, we may decide to introduce a runtime-based system. If so, then at that point, the new template grammar would need to be part of the formal specification of MaterialX. This would infer that downstream integrators of MaterialX would be required to support ingestion of a data library containing this new formalized template syntax, if they wanted to provide 100% MaterialX compliance.

Here we attempt to describe formally that syntax as it currently stands. We should note that it is possible the MaterialX project may opt to change this before it becomes formalized, or perhaps even decide that the template syntax should remain solely the domain of the build-time files, and that downstream consumers prefer to ingest the fully expanded data library files.

These named constants can be used anywhere a concrete value is accepted, ie. in any `value` or `default` attribute. The named constant is referenced by prefixing the name of the named value with `"Constant:"`.

```xml
<input name="in1" type="color3" value="Constant:zero"/>
Copy link
Member

@jstone-lucasfilm jstone-lucasfilm Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I see the logic behind the proposed Constant: prefix, we've historically used the : separator to represent the concept of namespaces in MaterialX, with brackets used to represent the concept of substitutions.

To my mind, this new mechanism seems closer in spirit to a substitution, and I'd be curious as to whether the following syntax might be clearer and more idiomatic to MaterialX:

  <input name="in1" type="color3" value="[zero]"/>
  <input name="in2" type="float" value="[one]"/>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For robustness I think its important to have an identifiable string prefix that we do not think will be used for any other part of the syntax.

In some sort of conceptual way, I think the current proposed syntax is a "namespace" of sorts - in as much as we're saying that the named constants "zero" "one" all belong to the "Constants:" namespace.

If we don't want to use the ":" token - I'm open to using something else - but I feel that having the "Constant" prefix in there makes the document clearer to read - as well as aids the robustness of implementation.

I do not think we would need the closing punctuation, ie. "]" in your example, as this construct completely replaces the value field. Including a closing punctuation implies you could write something like

<input name="in" type="vector3" value="[zero], [one], [zero]">

Which is not the intention of this named constants grammar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I prefer value="Constant:zero" because of explicit clarity. But I do appreciate that to people fluent in .mtlx syntax, seeing value="[zero]" feels natural and obvious, and I think it's important to keep things consistent, if they are relating to similar concepts.

There is however a certain ambiguity in value="[zero]" because there are several "zeros" to substitute with, depending on the type context. It's probably not a big deal, though.

Perhaps, value="color3:zero" makes it crystal clear what value is being used. But then the repetition of color3 is not ideal (for copy-pasting, etc):

<input name="in1" type="color3" value="color3:zero"/>

so generalizing it to

<input name="in1" type="color3" value="type:zero"/>

or value="typeconst:zero", but that's not much different from value="constant:zero". And I'm back to square one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha - @rafalSFX - I'm pretty sure I went around a similar circle more than once while putting this together, and always ended up coming back to something of the form <prefix>:<constantName>. I think the <prefix> part is super important for a few different reasons. It was the cleanest way I could find to make the document readable and easy to understand, and also I think that structure gives us the easiest and most robust thing to parse and evaluate, I tried others and the code was more complex or fragile.

I'm very happy to bike shed what the <prefix> string should be. I like Constant which was actually suggested by @bernardkwok , but I do also like your typeconst and I'm sure there are other very valid suggestions. I don't feel too strongly about which specific horse we pick in this race - but I do think this is the right race to pick a horse from.

I think it would be less helpful to have color3:zero, because in the template case that would end up being templated to something like @varName@:zero because we would need to support different type names in the template - this feels less clear to read to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we needed to implement this as a namespace prefix rather than a substitution, I'd be open to @rafalSFX's suggestion of type:zero, which clarifies that we're declaring a new string in the type namespace.

Taking a step back, though, I have a suggestion for an even more harmonized approach, which might give us the best of both worlds:

What if we were to declare these type substitutions using actual token elements, rather than adding a new constant element category to MaterialX?

The proposed syntax would look as follows:

<typedef name="float">
  <token name="zero" value="0.0"/>
  <token name="one" value="1.0"/>
</typedef>

<input name="in1" type="color3" value="[zero]"/>
<input name="in2" type="float" value="[one]"/>

With this approach, we would be directly leveraging the existing mechanism for token substitutions in MaterialX, and merely extending the declaration of token elements from nodedef parents to typedef parents.

The rules for handling overlapping tokens would remain exactly as before, and any future run-time implementation could be implemented by simply extending our existing codebase for string substitutions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my biggest worry with re-using the existing token system would be the clashing of token names as resolved up through the hierarchy. The intention and goal with the named constants is to provide a robust indirection to the concrete values. Working through the engineering for this, we've already discovered cases in the data library where mistakes were made. Overloading the token substitution system like this I feel we would be reducing the potential robustness of the system.

The current proposal is explicitly constrained to the value attribute of input/output elements, and by definition uses the corresponding type attribute to perform the resolution.

The current token substitution system is not currently tied to the type of the input, other than (I think) because it only operates on filename types values? So we may risk introducing complexity to the token substitution system if we overload it with multiple uses.

In general I think it's a good idea to not re-use syntax for different purposes, and instead am in favor of each different piece of grammar having a single specific purpose.

<typedef name="string">
  <token name="empty" value=""/>
</typedef>

<image name="myImg" type="color3">
  <token name="empty" value="MyEmptyTexture.png"/>
  <input name="file" type="filename" value="[empty]"/>
</image>

This is an example that I feel could be pretty confusing to the user.

The final complication I see with re-using the token substitution is that it becomes pretty unclear how to provide a build-time expansion of the named values. One of the concrete goals of this work is to be able to deliver a data library that is unmodified to downstream consumers. If we're using token substitution, how would we indicate which of these tokens should be substituted at build time? I don't believe we would want to expand everything, because that would defeat the purpose of the current token substitution system.


```xml
<template name="TP_ND_multiply" varames="typeName" options="(float, color3, vector4)">
<nodedef name="ND_multiply_@typeName@" node="multiply" nodegroup="math">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the same spirit as the notes above, these new generic types seem analogous to the substitution mechanisms available in MaterialX, and the following syntax might be clearer and better harmonized with existing conventions:

<nodedef name="ND_multiply_[typeName]" node="multiply" nodegroup="math">

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very open to alternate punctuation other than "@" - that was a fairly arbitrary choice made, just to be able to move on with implementation. But I do feel strongly that if we want things to be robust we need to have different punctuation, otherwise what happens if the existing substitution is used in the same context as the template expansion.

I'm not sure I'm super familiar with the substitution mechanism you're referring to here - can you perhaps provide a concrete example of a materialx document that is using it here, so we can compare the two syntaxes together.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ld-kerley Here is the current section on Token elements in MaterialX, which represent arbitrary key/value pairs for filename substitutions within NodeDef elements:

https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/documents/Specification/MaterialX.Specification.md#nodedef-token-elements

And here's the broader section on Filename Substitutions, which might naturally extend to value and type substitutions for the named constant and templated type functionality in this proposal:

https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/documents/Specification/MaterialX.Specification.md#filename-substitutions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the example referenced from the documentation - its unclear to me how if token substitution and templates were used in the same construct how you would differentiate the two instances of [ and ].

Do you have any concrete ideas there?

Copy link
Contributor

@rafalSFX rafalSFX Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, token substitution occurs only in string input values, such as file names. It is not currently performed in the name XML attribute. So I don't think there is any ambiguity currently. But I share your concern, and even though currently there is no issue, we should be careful about the future.

Also, there is a question whether we want to substitute the current type (typeName) in the input sting values, which would indeed lead to a serious ambiguity.

In the file name substitution link above, in addition to [], there are <> and {} substitutions too. So it looks like @@ is an available choice.

Another, more verbose option would be to introduce new attribute names available in the templates:

<nodedef nameprefix="ND_multiply_" namesuffix="_foo" node="multiply" nodegroup="math">

resulting in name="ND_multipy_vector3_foo" in the stamped-out nodedef. But it looks complicated/convoluted to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rafalSFX - thanks for the input - the name prefix/suffix idea is one worth considering. I'd need to look through all the templates I've made so far and see if the would be sufficient. If I'm understanding your suggestion here, you're only proposing using this for the name attribute, and we would still need/use some sort of @varName@ wrapping to do the substitutions else where in the template elements? I might worry a little that we end up with two different mechanisms to do something that we could just achieve with just the @varName@ substitution.

I'll also note that currently we are applying the template variable substitution in a few different attributes.

  • The name of a nodeDef element.
  • The type and value of an input.
  • I think there are even nested template cases where we're replacing parts of the template attributes inside a nested template.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the prefix/suffix solution would target just the name attribute. And because, as you point out, there are other places that need substitution, then indeed it's not an optimal approach. A single substitution mechanism that applies to all the places is more consistent and robust, and thus -- preferred.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants