Files
SDL3CPlusPlus/MaterialX/source/MaterialXCore/Version.cpp
2026-01-06 13:25:49 +00:00

1435 lines
61 KiB
C++

//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//
#include <MaterialXCore/Document.h>
MATERIALX_NAMESPACE_BEGIN
namespace
{
// Return the NodeDef associated with a legacy ShaderRef element.
NodeDefPtr getShaderNodeDef(ElementPtr shaderRef)
{
if (shaderRef->hasAttribute(NodeDef::NODE_DEF_ATTRIBUTE))
{
string nodeDefString = shaderRef->getAttribute(NodeDef::NODE_DEF_ATTRIBUTE);
ConstDocumentPtr doc = shaderRef->getDocument();
NodeDefPtr child = doc->getNodeDef(shaderRef->getQualifiedName(nodeDefString));
return child ? child : doc->getNodeDef(nodeDefString);
}
if (shaderRef->hasAttribute(NodeDef::NODE_ATTRIBUTE))
{
string nodeString = shaderRef->getAttribute(NodeDef::NODE_ATTRIBUTE);
string type = shaderRef->getAttribute(TypedElement::TYPE_ATTRIBUTE);
string target = shaderRef->getAttribute(InterfaceElement::TARGET_ATTRIBUTE);
string version = shaderRef->getAttribute(InterfaceElement::VERSION_ATTRIBUTE);
vector<NodeDefPtr> nodeDefs = shaderRef->getDocument()->getMatchingNodeDefs(shaderRef->getQualifiedName(nodeString));
vector<NodeDefPtr> secondary = shaderRef->getDocument()->getMatchingNodeDefs(nodeString);
nodeDefs.insert(nodeDefs.end(), secondary.begin(), secondary.end());
for (NodeDefPtr nodeDef : nodeDefs)
{
if (targetStringsMatch(nodeDef->getTarget(), target) &&
nodeDef->isVersionCompatible(version) &&
(type.empty() || nodeDef->getType() == type))
{
return nodeDef;
}
}
}
return NodeDefPtr();
}
// Copy an input between nodes, maintaining all existing bindings.
void copyInputWithBindings(NodePtr sourceNode, const string& sourceInputName,
NodePtr destNode, const string& destInputName)
{
InputPtr sourceInput = sourceNode->getInput(sourceInputName);
if (sourceInput)
{
InputPtr destInput = destNode->getInput(destInputName);
if (!destInput)
{
destInput = destNode->addInput(destInputName, sourceInput->getType());
}
destInput->copyContentFrom(sourceInput);
}
}
} // anonymous namespace
void Document::upgradeVersion()
{
std::pair<int, int> documentVersion = getVersionIntegers();
std::pair<int, int> expectedVersion(MATERIALX_MAJOR_VERSION, MATERIALX_MINOR_VERSION);
if (documentVersion >= expectedVersion)
{
return;
}
int majorVersion = documentVersion.first;
int minorVersion = documentVersion.second;
// Upgrade from v1.22 to v1.23
if (majorVersion == 1 && minorVersion == 22)
{
for (ElementPtr elem : traverseTree())
{
if (elem->getAttribute(TypedElement::TYPE_ATTRIBUTE) == "vector")
{
elem->setAttribute(TypedElement::TYPE_ATTRIBUTE, getTypeString<Vector3>());
}
}
minorVersion = 23;
}
// Upgrade from v1.23 to v1.24
if (majorVersion == 1 && minorVersion == 23)
{
for (ElementPtr elem : traverseTree())
{
if (elem->getCategory() == "shader" && elem->hasAttribute("shadername"))
{
elem->setAttribute(NodeDef::NODE_ATTRIBUTE, elem->getAttribute("shadername"));
elem->removeAttribute("shadername");
}
for (ElementPtr child : getChildrenOfType<Element>("assign"))
{
elem->changeChildCategory(child, "materialassign");
}
}
minorVersion = 24;
}
// Upgrade from v1.24 to v1.25
if (majorVersion == 1 && minorVersion == 24)
{
for (ElementPtr elem : traverseTree())
{
if (elem->isA<Input>() && elem->hasAttribute("graphname"))
{
elem->setAttribute("opgraph", elem->getAttribute("graphname"));
elem->removeAttribute("graphname");
}
}
minorVersion = 25;
}
// Upgrade from v1.25 to v1.26
if (majorVersion == 1 && minorVersion == 25)
{
for (ElementPtr elem : traverseTree())
{
if (elem->getCategory() == "constant")
{
ElementPtr param = elem->getChild("color");
if (param)
{
param->setName("value");
}
}
}
minorVersion = 26;
}
// Upgrade from v1.26 to v1.34
if (majorVersion == 1 && minorVersion == 26)
{
// Upgrade elements in place.
for (ElementPtr elem : traverseTree())
{
ElementVec origChildren = elem->getChildren();
for (ElementPtr child : origChildren)
{
if (child->getCategory() == "opgraph")
{
elem->changeChildCategory(child, "nodegraph");
}
else if (child->getCategory() == "shader")
{
NodeDefPtr nodeDef = elem->changeChildCategory(child, "nodedef")->asA<NodeDef>();
if (nodeDef->hasAttribute("shadertype"))
{
nodeDef->setType(SURFACE_SHADER_TYPE_STRING);
}
if (nodeDef->hasAttribute("shaderprogram"))
{
nodeDef->setNodeString(nodeDef->getAttribute("shaderprogram"));
}
}
else if (child->getCategory() == "shaderref")
{
if (child->hasAttribute("shadertype"))
{
child->setAttribute(TypedElement::TYPE_ATTRIBUTE, SURFACE_SHADER_TYPE_STRING);
child->removeAttribute("shadertype");
}
}
else if (child->getCategory() == "parameter")
{
if (child->getAttribute(TypedElement::TYPE_ATTRIBUTE) == "opgraphnode")
{
if (elem->isA<Node>())
{
InputPtr input = elem->changeChildCategory(child, "input")->asA<Input>();
input->setNodeName(input->getAttribute("value"));
input->removeAttribute("value");
if (input->getConnectedNode())
{
input->setType(input->getConnectedNode()->getType());
}
else
{
input->setType(getTypeString<Color3>());
}
}
else if (elem->isA<Output>())
{
if (child->getName() == "in")
{
elem->setAttribute("nodename", child->getAttribute("value"));
}
elem->removeChild(child->getName());
}
}
}
}
}
// Assign nodedef names to shaderrefs.
for (ElementPtr mat : getChildrenOfType<Element>("material"))
{
for (ElementPtr shaderRef : mat->getChildrenOfType<Element>("shaderref"))
{
if (!getShaderNodeDef(shaderRef))
{
NodeDefPtr nodeDef = getNodeDef(shaderRef->getName());
if (nodeDef)
{
shaderRef->setAttribute(NodeDef::NODE_DEF_ATTRIBUTE, nodeDef->getName());
shaderRef->setAttribute(NodeDef::NODE_ATTRIBUTE, nodeDef->getNodeString());
}
}
}
}
// Move connections from nodedef inputs to bindinputs.
ElementVec materials = getChildrenOfType<Element>("material");
for (NodeDefPtr nodeDef : getNodeDefs())
{
for (InputPtr input : nodeDef->getActiveInputs())
{
if (input->hasAttribute("opgraph") && input->hasAttribute("graphoutput"))
{
for (ElementPtr mat : materials)
{
for (ElementPtr shaderRef : mat->getChildrenOfType<Element>("shaderref"))
{
if (getShaderNodeDef(shaderRef) == nodeDef && !shaderRef->getChild(input->getName()))
{
ElementPtr bindInput = shaderRef->addChildOfCategory("bindinput", input->getName());
bindInput->setAttribute(TypedElement::TYPE_ATTRIBUTE, input->getType());
bindInput->setAttribute("nodegraph", input->getAttribute("opgraph"));
bindInput->setAttribute("output", input->getAttribute("graphoutput"));
}
}
}
input->removeAttribute("opgraph");
input->removeAttribute("graphoutput");
}
}
}
// Combine udim assignments into udim sets.
for (GeomInfoPtr geomInfo : getGeomInfos())
{
for (ElementPtr child : geomInfo->getChildrenOfType<Element>("geomattr"))
{
geomInfo->changeChildCategory(child, "geomprop");
}
}
if (getGeomPropValue("udim") && !getGeomPropValue("udimset"))
{
StringSet udimSet;
for (GeomInfoPtr geomInfo : getGeomInfos())
{
for (GeomPropPtr geomProp : geomInfo->getGeomProps())
{
if (geomProp->getName() == "udim")
{
udimSet.insert(geomProp->getValueString());
}
}
}
string udimSetString;
for (const string& udim : udimSet)
{
if (udimSetString.empty())
{
udimSetString = udim;
}
else
{
udimSetString += ", " + udim;
}
}
GeomInfoPtr udimSetInfo = addGeomInfo();
udimSetInfo->setGeomPropValue(UDIM_SET_PROPERTY, udimSetString, getTypeString<StringVec>());
}
minorVersion = 34;
}
// Upgrade from v1.34 to v1.35
if (majorVersion == 1 && minorVersion == 34)
{
for (ElementPtr elem : traverseTree())
{
if (elem->getAttribute(TypedElement::TYPE_ATTRIBUTE) == "matrix")
{
elem->setAttribute(TypedElement::TYPE_ATTRIBUTE, getTypeString<Matrix44>());
}
if (elem->hasAttribute("default") && !elem->hasAttribute(ValueElement::VALUE_ATTRIBUTE))
{
elem->setAttribute(ValueElement::VALUE_ATTRIBUTE, elem->getAttribute("default"));
elem->removeAttribute("default");
}
MaterialAssignPtr matAssign = elem->asA<MaterialAssign>();
if (matAssign)
{
matAssign->setMaterial(matAssign->getName());
}
}
minorVersion = 35;
}
// Upgrade from v1.35 to v1.36
if (majorVersion == 1 && minorVersion == 35)
{
for (ElementPtr elem : traverseTree())
{
LookPtr look = elem->asA<Look>();
GeomInfoPtr geomInfo = elem->asA<GeomInfo>();
if (elem->getAttribute(TypedElement::TYPE_ATTRIBUTE) == GEOMNAME_TYPE_STRING &&
elem->getAttribute(ValueElement::VALUE_ATTRIBUTE) == "*")
{
elem->setAttribute(ValueElement::VALUE_ATTRIBUTE, UNIVERSAL_GEOM_NAME);
}
if (elem->getAttribute(TypedElement::TYPE_ATTRIBUTE) == FILENAME_TYPE_STRING)
{
StringMap stringMap;
stringMap["%UDIM"] = UDIM_TOKEN;
stringMap["%UVTILE"] = UV_TILE_TOKEN;
elem->setAttribute(ValueElement::VALUE_ATTRIBUTE, replaceSubstrings(elem->getAttribute(ValueElement::VALUE_ATTRIBUTE), stringMap));
}
ElementVec origChildren = elem->getChildren();
for (ElementPtr child : origChildren)
{
if (elem->getCategory() == "material" && child->getCategory() == "override")
{
for (ElementPtr shaderRef : elem->getChildrenOfType<Element>("shaderref"))
{
NodeDefPtr nodeDef = getShaderNodeDef(shaderRef);
if (nodeDef)
{
for (ValueElementPtr activeValue : nodeDef->getActiveValueElements())
{
if (activeValue->getAttribute("publicname") == child->getName() &&
!shaderRef->getChild(child->getName()))
{
if (activeValue->getCategory() == "parameter")
{
ElementPtr bindParam = shaderRef->addChildOfCategory("bindparam", activeValue->getName());
bindParam->setAttribute(TypedElement::TYPE_ATTRIBUTE, activeValue->getType());
bindParam->setAttribute(ValueElement::VALUE_ATTRIBUTE, child->getAttribute("value"));
}
else if (activeValue->isA<Input>())
{
ElementPtr bindInput = shaderRef->addChildOfCategory("bindinput", activeValue->getName());
bindInput->setAttribute(TypedElement::TYPE_ATTRIBUTE, activeValue->getType());
bindInput->setAttribute(ValueElement::VALUE_ATTRIBUTE, child->getAttribute("value"));
}
}
}
}
}
elem->removeChild(child->getName());
}
else if (elem->getCategory() == "material" && child->getCategory() == "materialinherit")
{
elem->setInheritString(child->getAttribute("material"));
elem->removeChild(child->getName());
}
else if (look && child->getCategory() == "lookinherit")
{
elem->setInheritString(child->getAttribute("look"));
elem->removeChild(child->getName());
}
}
}
minorVersion = 36;
}
// Upgrade from 1.36 to 1.37
if (majorVersion == 1 && minorVersion == 36)
{
// Convert type attributes to child outputs.
for (NodeDefPtr nodeDef : getNodeDefs())
{
InterfaceElementPtr interfaceElem = std::static_pointer_cast<InterfaceElement>(nodeDef);
if (interfaceElem && interfaceElem->hasType())
{
string type = interfaceElem->getAttribute(TypedElement::TYPE_ATTRIBUTE);
OutputPtr outputPtr;
if (!type.empty() && type != MULTI_OUTPUT_TYPE_STRING)
{
outputPtr = interfaceElem->getOutput("out");
if (!outputPtr)
{
outputPtr = interfaceElem->addOutput("out", type);
}
}
interfaceElem->removeAttribute(TypedElement::TYPE_ATTRIBUTE);
const string& defaultInput = interfaceElem->getAttribute(Output::DEFAULT_INPUT_ATTRIBUTE);
if (outputPtr && !defaultInput.empty())
{
outputPtr->setAttribute(Output::DEFAULT_INPUT_ATTRIBUTE, defaultInput);
}
interfaceElem->removeAttribute(Output::DEFAULT_INPUT_ATTRIBUTE);
}
}
// Remove legacy shader nodedefs.
for (NodeDefPtr nodeDef : getNodeDefs())
{
if (nodeDef->hasAttribute("shadertype"))
{
for (ElementPtr mat : getChildrenOfType<Element>("material"))
{
for (ElementPtr shaderRef : mat->getChildrenOfType<Element>("shaderref"))
{
if (shaderRef->getAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE) == nodeDef->getName())
{
shaderRef->removeAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE);
}
}
}
removeNodeDef(nodeDef->getName());
}
}
// Convert geometric attributes to geometric properties.
for (GeomInfoPtr geomInfo : getGeomInfos())
{
for (ElementPtr child : geomInfo->getChildrenOfType<Element>("geomattr"))
{
geomInfo->changeChildCategory(child, "geomprop");
}
}
for (ElementPtr elem : traverseTree())
{
NodePtr node = elem->asA<Node>();
if (!node)
{
continue;
}
if (node->getCategory() == "geomattrvalue")
{
node->setCategory("geompropvalue");
if (node->hasAttribute("attrname"))
{
node->setAttribute("geomprop", node->getAttribute("attrname"));
node->removeAttribute("attrname");
}
}
}
vector<NodePtr> unusedNodes;
for (ElementPtr elem : traverseTree())
{
NodePtr node = elem->asA<Node>();
if (!node)
{
continue;
}
const string& nodeCategory = node->getCategory();
// Change category from "invert to "invertmatrix" for matrix invert nodes
if (nodeCategory == "invert" &&
(node->getType() == getTypeString<Matrix33>() || node->getType() == getTypeString<Matrix44>()))
{
node->setCategory("invertmatrix");
}
// Change category from "rotate" to "rotate2d" or "rotate3d" nodes
else if (nodeCategory == "rotate")
{
node->setCategory((node->getType() == getTypeString<Vector2>()) ? "rotate2d" : "rotate3d");
}
// Convert "compare" node to "ifgreatereq".
else if (nodeCategory == "compare")
{
node->setCategory("ifgreatereq");
InputPtr intest = node->getInput("intest");
if (intest)
{
intest->setName("value1");
}
ElementPtr cutoff = node->getChild("cutoff");
if (cutoff)
{
cutoff = node->changeChildCategory(cutoff, "input");
cutoff->setName("value2");
}
InputPtr in1 = node->getInput("in1");
InputPtr in2 = node->getInput("in2");
if (in1 && in2)
{
in1->setName(createValidChildName("temp"));
in2->setName("in1");
in1->setName("in2");
}
}
// Change nodes with category "transform[vector|point|normal]",
// which are not fromspace/tospace variants, to "transformmatrix"
else if (nodeCategory == "transformpoint" ||
nodeCategory == "transformvector" ||
nodeCategory == "transformnormal")
{
if (!node->getChild("fromspace") && !node->getChild("tospace"))
{
node->setCategory("transformmatrix");
}
}
// Convert "combine" to "combine2", "combine3" or "combine4"
else if (nodeCategory == "combine")
{
if (node->getChild("in4"))
{
node->setCategory("combine4");
}
else if (node->getChild("in3"))
{
node->setCategory("combine3");
}
else
{
node->setCategory("combine2");
}
}
// Convert "separate" to "separate2", "separate3" or "separate4"
else if (nodeCategory == "separate")
{
InputPtr in = node->getInput("in");
if (in)
{
const string& inType = in->getType();
if (inType == getTypeString<Vector4>() || inType == getTypeString<Color4>())
{
node->setCategory("separate4");
}
else if (inType == getTypeString<Vector3>() || inType == getTypeString<Color3>())
{
node->setCategory("separate3");
}
else
{
node->setCategory("separate2");
}
}
}
// Convert backdrop nodes to backdrop elements
else if (nodeCategory == "backdrop")
{
BackdropPtr backdrop = addBackdrop(node->getName());
for (ElementPtr child : node->getChildrenOfType<Element>("parameter"))
{
if (child->hasAttribute(ValueElement::VALUE_ATTRIBUTE))
{
backdrop->setAttribute(child->getName(), child->getAttribute(ValueElement::VALUE_ATTRIBUTE));
}
}
unusedNodes.push_back(node);
}
}
for (NodePtr node : unusedNodes)
{
node->getParent()->removeChild(node->getName());
}
minorVersion = 37;
}
// Upgrade from 1.37 to 1.38
if (majorVersion == 1 && minorVersion == 37)
{
// Convert color2 types to vector2
const StringMap COLOR2_CHANNEL_MAP = { { "r", "x" }, { "a", "y" } };
for (ElementPtr elem : traverseTree())
{
if (elem->getAttribute(TypedElement::TYPE_ATTRIBUTE) == "color2")
{
elem->setAttribute(TypedElement::TYPE_ATTRIBUTE, getTypeString<Vector2>());
NodePtr parentNode = elem->getParent()->asA<Node>();
if (!parentNode)
{
continue;
}
for (PortElementPtr port : parentNode->getDownstreamPorts())
{
if (port->hasAttribute("channels"))
{
string channels = port->getAttribute("channels");
channels = replaceSubstrings(channels, COLOR2_CHANNEL_MAP);
port->setAttribute("channels", channels);
}
if (port->hasOutputString())
{
string output = port->getOutputString();
output = replaceSubstrings(output, COLOR2_CHANNEL_MAP);
port->setOutputString(output);
}
}
ElementPtr channels = parentNode->getChild("channels");
if (channels && channels->hasAttribute(ValueElement::VALUE_ATTRIBUTE))
{
string value = channels->getAttribute(ValueElement::VALUE_ATTRIBUTE);
value = replaceSubstrings(value, COLOR2_CHANNEL_MAP);
channels->setAttribute(ValueElement::VALUE_ATTRIBUTE, value);
}
}
}
// Convert material elements to material nodes
for (ElementPtr mat : getChildrenOfType<Element>("material"))
{
NodePtr materialNode = nullptr;
for (ElementPtr shaderRef : mat->getChildrenOfType<Element>("shaderref"))
{
NodeDefPtr nodeDef = getShaderNodeDef(shaderRef);
// Get the shader node type and category, using the shader nodedef if present.
string shaderNodeType = nodeDef ? nodeDef->getType() : SURFACE_SHADER_TYPE_STRING;
string shaderNodeCategory = nodeDef ? nodeDef->getNodeString() : shaderRef->getAttribute(NodeDef::NODE_ATTRIBUTE);
// Add the shader node.
string shaderNodeName = createValidChildName(shaderRef->getName());
NodePtr shaderNode = addNode(shaderNodeCategory, shaderNodeName, shaderNodeType);
// Copy attributes to the shader node.
string nodeDefString = shaderRef->getAttribute(NodeDef::NODE_DEF_ATTRIBUTE);
string target = shaderRef->getAttribute(InterfaceElement::TARGET_ATTRIBUTE);
string version = shaderRef->getAttribute(InterfaceElement::VERSION_ATTRIBUTE);
if (!nodeDefString.empty())
{
shaderNode->setNodeDefString(nodeDefString);
}
if (!target.empty())
{
shaderNode->setTarget(target);
}
if (!version.empty())
{
shaderNode->setVersionString(version);
}
shaderNode->setSourceUri(shaderRef->getSourceUri());
// Copy child elements to the shader node.
for (ElementPtr child : shaderRef->getChildren())
{
ElementPtr newChild;
if (child->getCategory() == "bindinput" || child->getCategory() == "bindparam")
{
newChild = shaderNode->addInput(child->getName());
}
else if (child->getCategory() == "bindtoken")
{
newChild = shaderNode->addToken(child->getName());
}
if (newChild)
{
newChild->copyContentFrom(child);
}
}
// Create a material node if needed, making a connection to the new shader node.
if (!materialNode)
{
materialNode = addMaterialNode(createValidName("temp"), shaderNode);
materialNode->setSourceUri(mat->getSourceUri());
}
// Assign additional shader inputs to the material as needed.
if (!materialNode->getInput(shaderNodeType))
{
InputPtr shaderInput = materialNode->addInput(shaderNodeType, shaderNodeType);
shaderInput->setNodeName(shaderNode->getName());
}
}
// Remove the material element, transferring its name and attributes to the material node.
removeChild(mat->getName());
if (materialNode)
{
materialNode->setName(mat->getName());
for (const string& attr : mat->getAttributeNames())
{
if (!materialNode->hasAttribute(attr))
{
materialNode->setAttribute(attr, mat->getAttribute(attr));
}
}
}
}
// Define BSDF node pairs.
using StringPair = std::pair<string, string>;
const StringPair DIELECTRIC_BRDF = { "dielectric_brdf", "dielectric_bsdf" };
const StringPair DIELECTRIC_BTDF = { "dielectric_btdf", "dielectric_bsdf" };
const StringPair GENERALIZED_SCHLICK_BRDF = { "generalized_schlick_brdf", "generalized_schlick_bsdf" };
const StringPair CONDUCTOR_BRDF = { "conductor_brdf", "conductor_bsdf" };
const StringPair SHEEN_BRDF = { "sheen_brdf", "sheen_bsdf" };
const StringPair DIFFUSE_BRDF = { "diffuse_brdf", "oren_nayar_diffuse_bsdf" };
const StringPair BURLEY_DIFFUSE_BRDF = { "burley_diffuse_brdf", "burley_diffuse_bsdf" };
const StringPair DIFFUSE_BTDF = { "diffuse_btdf", "translucent_bsdf" };
const StringPair SUBSURFACE_BRDF = { "subsurface_brdf", "subsurface_bsdf" };
const StringPair THIN_FILM_BRDF = { "thin_film_brdf", "thin_film_bsdf" };
// Function for upgrading old nested layering setup
// to new setup with layer operators.
auto upgradeBsdfLayering = [](NodePtr node)
{
InputPtr base = node->getInput("base");
if (base)
{
NodePtr baseNode = base->getConnectedNode();
if (baseNode)
{
GraphElementPtr parent = node->getParent()->asA<GraphElement>();
// Rename the top bsdf node, and give its old name to the layer operator
// so we don't need to update any connection references.
const string oldName = node->getName();
node->setName(oldName + "__layer_top");
NodePtr layer = parent->addNode("layer", oldName, "BSDF");
InputPtr layerTop = layer->addInput("top", "BSDF");
InputPtr layerBase = layer->addInput("base", "BSDF");
layerTop->setConnectedNode(node);
layerBase->setConnectedNode(baseNode);
}
node->removeInput("base");
}
};
// Storage for inputs found connected downstream from artistic_ior node.
vector<InputPtr> artisticIorConnections, artisticExtConnections;
// Update all nodes.
for (ElementPtr elem : traverseTree())
{
NodePtr node = elem->asA<Node>();
if (!node)
{
continue;
}
const string& nodeCategory = node->getCategory();
if (nodeCategory == "atan2")
{
InputPtr input = node->getInput("in1");
InputPtr input2 = node->getInput("in2");
if (input && input2)
{
input->setName(EMPTY_STRING);
input2->setName("in1");
input->setName("in2");
}
else
{
if (input)
{
input->setName("in2");
}
if (input2)
{
input2->setName("in1");
}
}
}
else if (nodeCategory == "rotate3d")
{
ElementPtr axis = node->getChild("axis");
if (axis)
{
node->changeChildCategory(axis, "input");
}
}
else if (nodeCategory == DIELECTRIC_BRDF.first)
{
node->setCategory(DIELECTRIC_BRDF.second);
upgradeBsdfLayering(node);
}
else if (nodeCategory == DIELECTRIC_BTDF.first)
{
node->setCategory(DIELECTRIC_BTDF.second);
node->removeInput("interior");
InputPtr mode = node->addInput("scatter_mode", STRING_TYPE_STRING);
mode->setValueString("T");
}
else if (nodeCategory == GENERALIZED_SCHLICK_BRDF.first)
{
node->setCategory(GENERALIZED_SCHLICK_BRDF.second);
upgradeBsdfLayering(node);
}
else if (nodeCategory == SHEEN_BRDF.first)
{
node->setCategory(SHEEN_BRDF.second);
upgradeBsdfLayering(node);
}
else if (nodeCategory == THIN_FILM_BRDF.first)
{
node->setCategory(THIN_FILM_BRDF.second);
upgradeBsdfLayering(node);
}
else if (nodeCategory == CONDUCTOR_BRDF.first)
{
node->setCategory(CONDUCTOR_BRDF.second);
// Create an artistic_ior node to convert from artistic to physical parameterization.
GraphElementPtr parent = node->getParent()->asA<GraphElement>();
NodePtr artisticIor = parent->addNode("artistic_ior", node->getName() + "__artistic_ior", "multioutput");
OutputPtr artisticIor_ior = artisticIor->addOutput("ior", "color3");
OutputPtr artisticIor_extinction = artisticIor->addOutput("extinction", "color3");
// Copy inputs and bindings from conductor node to artistic_ior node.
copyInputWithBindings(node, "reflectivity", artisticIor, "reflectivity");
copyInputWithBindings(node, "edge_color", artisticIor, "edge_color");
// Update the parameterization on the conductor node
// and connect it to the artistic_ior node.
node->removeInput("reflectivity");
node->removeInput("edge_color");
InputPtr ior = node->addInput("ior", "color3");
ior->setNodeName(artisticIor->getName());
ior->setOutputString(artisticIor_ior->getName());
InputPtr extinction = node->addInput("extinction", "color3");
extinction->setNodeName(artisticIor->getName());
extinction->setOutputString(artisticIor_extinction->getName());
}
else if (nodeCategory == DIFFUSE_BRDF.first)
{
node->setCategory(DIFFUSE_BRDF.second);
}
else if (nodeCategory == BURLEY_DIFFUSE_BRDF.first)
{
node->setCategory(BURLEY_DIFFUSE_BRDF.second);
}
else if (nodeCategory == DIFFUSE_BTDF.first)
{
node->setCategory(DIFFUSE_BTDF.second);
}
else if (nodeCategory == SUBSURFACE_BRDF.first)
{
node->setCategory(SUBSURFACE_BRDF.second);
}
else if (nodeCategory == "artistic_ior")
{
OutputPtr ior = node->getOutput("ior");
if (ior)
{
ior->setType("color3");
}
OutputPtr extinction = node->getOutput("extinction");
if (extinction)
{
extinction->setType("color3");
}
}
// Search for connections to artistic_ior with vector3 type.
// If found we must insert a conversion node color3->vector3
// since the outputs of artistic_ior is now color3.
// Save the inputs here and insert the conversion nodes below,
// since we can't modify the graph while traversing it.
for (InputPtr input : node->getInputs())
{
if (input->getOutputString() == "ior" && input->getType() == "vector3")
{
NodePtr connectedNode = input->getConnectedNode();
if (connectedNode && connectedNode->getCategory() == "artistic_ior")
{
artisticIorConnections.push_back(input);
}
}
else if (input->getOutputString() == "extinction" && input->getType() == "vector3")
{
NodePtr connectedNode = input->getConnectedNode();
if (connectedNode && connectedNode->getCategory() == "artistic_ior")
{
artisticExtConnections.push_back(input);
}
}
}
}
// Insert conversion nodes for artistic_ior connections found above.
for (InputPtr input : artisticIorConnections)
{
NodePtr artisticIorNode = input->getConnectedNode();
ElementPtr node = input->getParent();
GraphElementPtr parent = node->getParent()->asA<GraphElement>();
NodePtr convert = parent->addNode("convert", node->getName() + "__convert_ior", "vector3");
InputPtr convertInput = convert->addInput("in", "color3");
convertInput->setNodeName(artisticIorNode->getName());
convertInput->setOutputString("ior");
input->setNodeName(convert->getName());
input->removeAttribute(PortElement::OUTPUT_ATTRIBUTE);
}
for (InputPtr input : artisticExtConnections)
{
NodePtr artisticIorNode = input->getConnectedNode();
ElementPtr node = input->getParent();
GraphElementPtr parent = node->getParent()->asA<GraphElement>();
NodePtr convert = parent->addNode("convert", node->getName() + "__convert_extinction", "vector3");
InputPtr convertInput = convert->addInput("in", "color3");
convertInput->setNodeName(artisticIorNode->getName());
convertInput->setOutputString("extinction");
input->setNodeName(convert->getName());
input->removeAttribute(PortElement::OUTPUT_ATTRIBUTE);
}
// Make it so that interface names and nodes in a nodegraph are not duplicates
// If they are, rename the nodes.
for (NodeGraphPtr nodegraph : getNodeGraphs())
{
// Clear out any erroneously set version
nodegraph->removeAttribute(InterfaceElement::VERSION_ATTRIBUTE);
StringSet interfaceNames;
for (auto child : nodegraph->getChildren())
{
NodePtr node = child->asA<Node>();
if (node)
{
for (ValueElementPtr elem : node->getChildrenOfType<ValueElement>())
{
const string& interfaceName = elem->getInterfaceName();
if (!interfaceName.empty())
{
interfaceNames.insert(interfaceName);
}
}
}
}
for (string interfaceName : interfaceNames)
{
NodePtr node = nodegraph->getNode(interfaceName);
if (node)
{
string newNodeName = nodegraph->createValidChildName(interfaceName);
vector<MaterialX::PortElementPtr> downstreamPorts = node->getDownstreamPorts();
for (MaterialX::PortElementPtr downstreamPort : downstreamPorts)
{
if (downstreamPort->getNodeName() == interfaceName)
{
downstreamPort->setNodeName(newNodeName);
}
}
node->setName(newNodeName);
}
}
}
// Convert parameters to inputs, applying uniform markings to converted inputs
// of nodedefs.
for (ElementPtr elem : traverseTree())
{
if (elem->isA<InterfaceElement>())
{
for (ElementPtr param : elem->getChildrenOfType<Element>("parameter"))
{
InputPtr input = elem->changeChildCategory(param, "input")->asA<Input>();
if (elem->isA<NodeDef>())
{
input->setIsUniform(true);
}
}
}
}
minorVersion = 38;
}
// Upgrade from 1.38 to 1.39
if (majorVersion == 1 && minorVersion == 38)
{
const std::unordered_map<char, size_t> CHANNEL_INDEX_MAP =
{
{ 'r', 0 }, { 'g', 1 }, { 'b', 2 }, { 'a', 3 },
{ 'x', 0 }, { 'y', 1 }, { 'z', 2 }, { 'w', 3 }
};
const std::unordered_map<char, float> CHANNEL_CONSTANT_MAP =
{
{ '0', 0.0f }, { '1', 1.0f }
};
const std::unordered_map<string, size_t> CHANNEL_COUNT_MAP =
{
{ "float", 1 },
{ "color3", 3 }, { "color4", 4 },
{ "vector2", 2 }, { "vector3", 3 }, { "vector4", 4 }
};
const std::array<std::pair<string, size_t>, 10> CHANNEL_CONVERT_PATTERNS =
{ {
{ "rgb", 3 }, { "rgb", 4 }, { "rgba", 4 },
{ "xyz", 3 }, { "xyz", 4 }, { "xyzw", 4 },
{ "rr", 1 }, { "rrr", 1 },
{ "xx", 1 }, { "xxx", 1 }
} };
const std::array<std::pair<StringSet, string>, 3> CHANNEL_ATTRIBUTE_PATTERNS =
{ {
{ { "xx", "xxx", "xxxx" }, "float" },
{ { "xyz", "x", "y", "z" }, "vector3" },
{ { "rgba", "a" }, "color4" }
} };
// Convert channels attributes to legacy swizzle nodes, which are then converted
// to modern nodes in a second pass.
for (ElementPtr elem : traverseTree())
{
PortElementPtr port = elem->asA<PortElement>();
if (!port)
{
continue;
}
const string& channelString = port->getAttribute("channels");
if (channelString.empty())
{
continue;
}
// Determine the upstream type.
ElementPtr parent = port->getParent();
GraphElementPtr graph = port->getAncestorOfType<GraphElement>();
NodePtr upstreamNode = port->getConnectedNode();
string upstreamType = upstreamNode ? upstreamNode->getType() : EMPTY_STRING;
if (upstreamType.empty() || upstreamType == MULTI_OUTPUT_TYPE_STRING)
{
for (const auto& pair : CHANNEL_ATTRIBUTE_PATTERNS)
{
if (pair.first.count(channelString))
{
upstreamType = pair.second;
break;
}
}
if (upstreamType.empty() || upstreamType == MULTI_OUTPUT_TYPE_STRING)
{
upstreamType = (port->getType() == "color3") ? "color4" : "color3";
}
}
// Ignore the channels string for purely scalar connections.
if (upstreamType == getTypeString<float>() && port->getType() == getTypeString<float>())
{
port->removeAttribute("channels");
continue;
}
// Create the new swizzle node.
NodePtr swizzleNode = graph->addNode("swizzle", graph->createValidChildName("swizzle"), port->getType());
int childIndex = (parent->getParent() == graph) ? graph->getChildIndex(parent->getName()) : graph->getChildIndex(port->getName());
if (childIndex != -1)
{
graph->setChildIndex(swizzleNode->getName(), childIndex);
}
InputPtr in = swizzleNode->addInput("in");
in->copyContentFrom(port);
in->removeAttribute("channels");
in->setType(upstreamType);
swizzleNode->setInputValue("channels", channelString);
// Connect the original port to this node.
port->setConnectedNode(swizzleNode);
port->removeAttribute(PortElement::OUTPUT_ATTRIBUTE);
port->removeAttribute(PortElement::INTERFACE_NAME_ATTRIBUTE);
port->removeAttribute("channels");
// Update any nodegraph reference
if (graph)
{
const string& portNodeGraphString = port->getNodeGraphString();
if (!portNodeGraphString.empty())
{
const string& graphName = graph->getName();
if (graphName.empty())
{
port->removeAttribute(PortElement::NODE_GRAPH_ATTRIBUTE);
}
else if (graphName != portNodeGraphString)
{
port->setNodeGraphString(graphName);
}
}
}
}
// Update all nodes.
vector<NodePtr> unusedNodes;
for (ElementPtr elem : traverseTree())
{
NodePtr node = elem->asA<Node>();
if (!node)
{
continue;
}
const string& nodeCategory = node->getCategory();
if (nodeCategory == "layer")
{
// Convert layering of thin_film_bsdf nodes to thin-film parameters on the affected BSDF nodes.
NodePtr top = node->getConnectedNode("top");
NodePtr base = node->getConnectedNode("base");
if (top && base && top->getCategory() == "thin_film_bsdf")
{
// Apply thin-film parameters to all supported BSDF's upstream.
const StringSet BSDF_WITH_THINFILM = { "dielectric_bsdf", "conductor_bsdf", "generalized_schlick_bsdf" };
for (Edge edge : node->traverseGraph())
{
NodePtr upstream = edge.getUpstreamElement()->asA<Node>();
if (upstream && BSDF_WITH_THINFILM.count(upstream->getCategory()))
{
InputPtr scatterMode = upstream->getInput("scatter_mode");
if (!scatterMode || scatterMode->getValueString() != "T")
{
copyInputWithBindings(top, "thickness", upstream, "thinfilm_thickness");
copyInputWithBindings(top, "ior", upstream, "thinfilm_ior");
}
}
}
// Bypass the thin-film layer operator.
vector<MaterialX::PortElementPtr> downstreamPorts = node->getDownstreamPorts();
for (auto port : downstreamPorts)
{
port->setNodeName(base->getName());
}
// Mark original nodes as unused.
unusedNodes.push_back(node);
unusedNodes.push_back(top);
}
}
else if (nodeCategory == "subsurface_bsdf")
{
InputPtr radiusInput = node->getInput("radius");
if (radiusInput && radiusInput->getType() == "vector3")
{
GraphElementPtr graph = node->getAncestorOfType<GraphElement>();
NodePtr convertNode = graph->addNode("convert", graph->createValidChildName("convert"), "color3");
copyInputWithBindings(node, "radius", convertNode, "in");
radiusInput->setConnectedNode(convertNode);
radiusInput->setType("color3");
}
}
else if (nodeCategory == "switch")
{
// Upgrade switch nodes from 5 to 10 inputs, handling the fallback behavior for
// constant "which" values that were previously out of range.
InputPtr which = node->getInput("which");
if (which && which->hasValue())
{
auto whichValue = which->getValue();
if (whichValue->isA<int>() && whichValue->asA<int>() >= 5)
{
which->setValue(0);
}
else if (whichValue->isA<float>() && whichValue->asA<float>() >= 5)
{
which->setValue(0.0);
}
}
}
else if (nodeCategory == "swizzle")
{
InputPtr inInput = node->getInput("in");
const string sourceType = inInput ? inInput->getType() : "float";
if (CHANNEL_COUNT_MAP.count(sourceType) &&
CHANNEL_COUNT_MAP.count(node->getType()))
{
InputPtr channelsInput = node->getInput("channels");
string channelString = channelsInput ? channelsInput->getValueString() : EMPTY_STRING;
string destType = node->getType();
size_t sourceChannelCount = CHANNEL_COUNT_MAP.at(sourceType);
size_t destChannelCount = CHANNEL_COUNT_MAP.at(destType);
// Resolve the invalid case of having both a connection and a value
// by removing the value attribute.
if (inInput && inInput->hasValue())
{
if (inInput->hasNodeName() || inInput->hasNodeGraphString() || inInput->hasInterfaceName())
{
inInput->removeAttribute(ValueElement::VALUE_ATTRIBUTE);
}
}
// We convert to a constant node if "in" input is a constant value or does not exist:
bool convertToConstantNode = !inInput || inInput->hasValue();
// We also convert to a constant node if every destination
// channel is constant:
// eg: "ND_swizzle_color3_color3" node with
// "010" in the "channels" input.
if (!convertToConstantNode)
{
convertToConstantNode = true;
for (size_t i = 0; i < destChannelCount; i++)
{
if (i < channelString.size())
{
if (CHANNEL_CONSTANT_MAP.count(channelString[i]))
{
// Still in constant territory:
continue;
}
}
// Every other scenario: not constant
convertToConstantNode = false;
break;
}
}
if (convertToConstantNode)
{
// Replace swizzle with constant.
node->setCategory("constant");
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_constant_" + node->getType());
}
string valueString = inInput ? inInput->getValueString() : "0";
StringVec origValueTokens = splitString(valueString, ARRAY_VALID_SEPARATORS);
StringVec newValueTokens;
for (size_t i = 0; i < destChannelCount; i++)
{
if (i < channelString.size())
{
if (CHANNEL_INDEX_MAP.count(channelString[i]))
{
size_t index = CHANNEL_INDEX_MAP.at(channelString[i]);
if (index < origValueTokens.size())
{
newValueTokens.push_back(origValueTokens[index]);
continue;
}
}
else if (CHANNEL_CONSTANT_MAP.count(channelString[i]))
{
newValueTokens.push_back(std::to_string(CHANNEL_CONSTANT_MAP.at(channelString[i])));
continue;
}
}
// Invalid channel name, or missing channel name:
newValueTokens.push_back(origValueTokens[0]);
}
InputPtr valueInput = node->addInput("value", node->getType());
valueInput->setValueString(joinStrings(newValueTokens, ", "));
// This is the last place we need to check for nullptr for inInput.
if (inInput)
{
node->removeInput(inInput->getName());
}
}
else if (destChannelCount == 1)
{
// Replace swizzle with extract.
node->setCategory("extract");
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_extract_" + sourceType);
}
if (!channelString.empty() && CHANNEL_INDEX_MAP.count(channelString[0]))
{
node->setInputValue("index", (int) CHANNEL_INDEX_MAP.at(channelString[0]));
}
}
else if (sourceType != destType && std::find(CHANNEL_CONVERT_PATTERNS.begin(), CHANNEL_CONVERT_PATTERNS.end(),
std::make_pair(channelString, sourceChannelCount)) != CHANNEL_CONVERT_PATTERNS.end())
{
// Replace swizzle with convert.
node->setCategory("convert");
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_convert_" + sourceType + "_" + destType);
}
}
else if (sourceChannelCount == 1)
{
// Replace swizzle with combine.
node->setCategory("combine" + std::to_string(destChannelCount));
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_combine" + std::to_string(destChannelCount) + "_" + node->getType());
}
for (size_t i = 0; i < destChannelCount; i++)
{
InputPtr combineInInput = node->addInput(std::string("in") + std::to_string(i + 1), "float");
if (i < channelString.size() && CHANNEL_CONSTANT_MAP.count(channelString[i]))
{
combineInInput->setValue(CHANNEL_CONSTANT_MAP.at(channelString[i]));
}
else
{
copyInputWithBindings(node, inInput->getName(), node, combineInInput->getName());
}
}
node->removeInput(inInput->getName());
}
else
{
// Replace swizzle with separate and combine.
GraphElementPtr graph = node->getAncestorOfType<GraphElement>();
NodePtr separateNode = graph->addNode(std::string("separate") + std::to_string(sourceChannelCount),
graph->createValidChildName("separate"), MULTI_OUTPUT_TYPE_STRING);
int childIndex = graph->getChildIndex(node->getName());
if (childIndex != -1)
{
graph->setChildIndex(separateNode->getName(), childIndex);
}
node->setCategory("combine" + std::to_string(destChannelCount));
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_combine" + std::to_string(destChannelCount) + "_" + node->getType());
}
for (size_t i = 0; i < destChannelCount; i++)
{
InputPtr combineInInput = node->addInput(std::string("in") + std::to_string(i + 1), "float");
if (i < channelString.size())
{
if (CHANNEL_INDEX_MAP.count(channelString[i]))
{
combineInInput->setConnectedNode(separateNode);
combineInInput->setOutputString(std::string("out") + channelString[i]);
continue;
}
else if (CHANNEL_CONSTANT_MAP.count(channelString[i]))
{
combineInInput->setValue(CHANNEL_CONSTANT_MAP.at(channelString[i]));
continue;
}
}
// Invalid channel name, or missing channel name:
combineInInput->setConnectedNode(separateNode);
combineInInput->setOutputString(inInput->isColorType() ? "outr" : "outx");
}
copyInputWithBindings(node, inInput->getName(), separateNode, "in");
node->removeInput(inInput->getName());
}
// Remove the channels input from the converted node.
if (channelsInput)
{
node->removeInput(channelsInput->getName());
}
}
}
else if (nodeCategory == "atan2")
{
InputPtr input1 = node->getInput("in1");
if (input1)
{
input1->setName("iny");
}
InputPtr input2 = node->getInput("in2");
if (input2)
{
input2->setName("inx");
}
}
else if (nodeCategory == "normalmap")
{
InputPtr space = node->getInput("space");
if (space && space->getValueString() == "object")
{
// Replace object-space normalmap with a graph.
GraphElementPtr graph = node->getAncestorOfType<GraphElement>();
NodePtr multiply = graph->addNode("multiply", graph->createValidChildName("multiply"), "vector3");
copyInputWithBindings(node, "in", multiply, "in1");
multiply->setInputValue("in2", 2.0f);
NodePtr subtract = graph->addNode("subtract", graph->createValidChildName("subtract"), "vector3");
subtract->addInput("in1", "vector3")->setConnectedNode(multiply);
subtract->setInputValue("in2", 1.0f);
node->setCategory("normalize");
vector<InputPtr> origInputs = node->getInputs();
for (InputPtr input : origInputs)
{
node->removeChild(input->getName());
}
node->addInput("in", "vector3")->setConnectedNode(subtract);
// Update nodedef name if present.
if (node->hasNodeDefString())
{
node->setNodeDefString("ND_normalize_vector3");
}
}
else
{
// Clear tangent-space input.
node->removeInput("space");
// If the normal or tangent inputs are set and the bitangent input is not,
// the bitangent should be set to normalize(cross(N, T))
InputPtr normalInput = node->getInput("normal");
InputPtr tangentInput = node->getInput("tangent");
InputPtr bitangentInput = node->getInput("bitangent");
if ((normalInput || tangentInput) && !bitangentInput)
{
GraphElementPtr graph = node->getAncestorOfType<GraphElement>();
NodePtr crossNode = graph->addNode("crossproduct", graph->createValidChildName("normalmap_cross"), "vector3");
copyInputWithBindings(node, "normal", crossNode, "in1");
copyInputWithBindings(node, "tangent", crossNode, "in2");
NodePtr normalizeNode = graph->addNode("normalize", graph->createValidChildName("normalmap_cross_norm"), "vector3");
normalizeNode->addInput("in", "vector3")->setConnectedNode(crossNode);
node->addInput("bitangent", "vector3")->setConnectedNode(normalizeNode);
}
// Update nodedef name if present.
if (node->getNodeDefString() == "ND_normalmap")
{
node->setNodeDefString("ND_normalmap_float");
}
}
}
}
for (NodePtr node : unusedNodes)
{
node->getParent()->removeChild(node->getName());
}
minorVersion = 39;
}
std::pair<int, int> upgradedVersion(majorVersion, minorVersion);
if (upgradedVersion == expectedVersion)
{
setVersionIntegers(majorVersion, minorVersion);
}
}
MATERIALX_NAMESPACE_END