Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
019edba
Fix discovery of tokens to accumulate current tokens through traversa…
mialana May 14, 2026
cc15276
Merge branch 'main' into support-token-editing
mialana May 14, 2026
3709caf
Implement token data as persistent map on ui node class
mialana May 15, 2026
18b0081
Write out edited token value and store affected inputs per token
mialana May 20, 2026
ee565f7
Fix for material render preview immediate update and resolve value st…
mialana May 20, 2026
090bf5c
Merge branch 'main' into support-token-editing
mialana May 20, 2026
b526ec7
Clean up comments and allow for tooltips per column in tokens table
mialana May 20, 2026
7e81ef7
Create help marker for tokens table, fill in all column tooltips
mialana May 20, 2026
b3cee30
Lint, supplementary comment
mialana May 20, 2026
04f9d02
Match private member function naming convention
mialana May 20, 2026
8d0767b
Move token mapping logic to an inline lambda to avoid repeated code
mialana May 20, 2026
33fe3a7
Handle tokens defined on custom node instances
mialana May 20, 2026
3644bd6
Move inline lambda token mapping handler to static member of UiToken …
mialana May 23, 2026
0a6f9e5
Merge branch 'main' into support-token-editing
jstone-lucasfilm May 26, 2026
7e9717e
Clean up minor code practice
mialana May 27, 2026
74b237a
Merge branch 'main' into support-token-editing
jstone-lucasfilm May 28, 2026
67130cd
Remove std::atomic usage due to no concurrency issues
mialana May 30, 2026
3bc659e
Merge branch 'support-token-editing' of github.com:mialana/FORK-Mater…
mialana May 30, 2026
360fcb5
Merge branch 'main' into support-token-editing
mialana May 30, 2026
2f41f79
Merge branch 'main' into support-token-editing
mialana Jun 12, 2026
21b7828
Merge branch 'main' into support-token-editing
jstone-lucasfilm Jun 13, 2026
6bb8add
Clear string stream correctly, add protection check in building affec…
mialana Jun 15, 2026
e2e2126
Merge branch 'support-token-editing' of github.com:mialana/FORK-Mater…
mialana Jun 15, 2026
400f793
Merge branch 'main' into support-token-editing
mialana Jun 15, 2026
928a720
Protect against null errors by adding early return in ui token mappin…
mialana Jun 16, 2026
cf673c9
Merge branch 'support-token-editing' of github.com:mialana/FORK-Mater…
mialana Jun 16, 2026
a778cc6
Fix cast from element to node rather than node to node
mialana Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 70 additions & 33 deletions source/MaterialXGraphEditor/Graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,10 +774,7 @@ void Graph::updateMaterials(mx::InputPtr input /* = nullptr */, mx::ValuePtr val
{
if (!input)
{
mx::ElementPtr elem = nullptr;
{
elem = _graphDoc->getDescendant(renderablePath);
}
const mx::ElementPtr elem = _graphDoc->getDescendant(renderablePath);
mx::TypedElementPtr typedElem = elem ? elem->asA<mx::TypedElement>() : nullptr;
_renderer->updateMaterials(typedElem);
}
Expand Down Expand Up @@ -1086,6 +1083,8 @@ void Graph::showPropertyEditorValue(UiNodePtr node, mx::InputPtr input, const mx
nodeInput->setValueString(temp);
nodeInput->setValue(temp, nodeInput->getType());
updateMaterials();

_currUiNode->buildUiTokenMap(); // Re-build token map
}
}
}
Expand Down Expand Up @@ -1139,16 +1138,16 @@ void Graph::setUiNodeInfo(UiNodePtr node, const std::string& type, const std::st
}
else
{
if (node->getNode())
if (mx::ConstNodePtr mxNode = node->getNode())
{
mx::NodeDefPtr nodeDef = node->getNode()->getNodeDef(node->getNode()->getName());
mx::NodeDefPtr nodeDef = mxNode->getNodeDef(mxNode->getName());
if (nodeDef)
{
for (mx::InputPtr input : nodeDef->getActiveInputs())
{
if (node->getNode()->getInput(input->getName()))
if (mxNode->getInput(input->getName()))
{
input = node->getNode()->getInput(input->getName());
input = mxNode->getInput(input->getName());
}
UiPinPtr inPin = std::make_shared<UiPin>(_state.nextUiId, node, ax::NodeEditor::PinKind::Input, input);
node->getInputPins().push_back(inPin);
Expand All @@ -1158,16 +1157,18 @@ void Graph::setUiNodeInfo(UiNodePtr node, const std::string& type, const std::st

for (mx::OutputPtr output : nodeDef->getActiveOutputs())
{
if (node->getNode()->getOutput(output->getName()))
if (mxNode->getOutput(output->getName()))
{
output = node->getNode()->getOutput(output->getName());
output = mxNode->getOutput(output->getName());
}
UiPinPtr outPin = std::make_shared<UiPin>(_state.nextUiId, node, ax::NodeEditor::PinKind::Output, output);
node->getOutputPins().push_back(outPin);
_state.pins.push_back(outPin);
++_state.nextUiId;
}
}

node->buildUiTokenMap(); // Build initial token map
}
else if (node->getInput())
{
Expand Down Expand Up @@ -3778,40 +3779,62 @@ void Graph::propertyEditor()
showPropertyEditorOutputConnections(_currUiNode);
}

// Find tokens within currUiNode
mx::ConstNodePtr node = _currUiNode->getNode();
if (node != nullptr)
// Draw token table
if (const auto& currTokenMap = _currUiNode->getUiTokenMap(); !currTokenMap.empty())
{
mx::StringResolverPtr resolver = node->createStringResolver();
const mx::StringMap& tokens = resolver->getFilenameSubstitutions();
ImGui::Text("Tokens");
ImGui::SameLine();
drawHelpMarker("All tokens that are within scope of the selected node. Token values will be string-substituted into listed 'Affected Inputs'.");

if (!tokens.empty())
int tokenCount = static_cast<int>(currTokenMap.size() + 1u); // Add 1 to account for header row
ImVec2 tableHeight(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, tokenCount));

// Use `ImGuiTableFlags_SizingFixedFit` to set default column width to fit content
if (ImGui::BeginTable("tokens_node_table", 4, tableFlags | ImGuiTableFlags_SizingFixedFit, tableHeight))
{
ImGui::Text("Tokens");
ImGui::SetWindowFontScale(_fontScale);

ImVec2 tableSize(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, static_cast<int>(tokens.size())));
bool haveTable = ImGui::BeginTable("tokens_node_table", 2, tableFlags, tableSize);
if (haveTable)
{
ImGui::SetWindowFontScale(_fontScale);
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Value");
ImGui::TableSetupColumn("Source Element");
ImGui::TableSetupColumn("Affected Inputs");

for (const auto& [token, value] : tokens)
{
// Set tooltips for each of table's columns
constexpr std::array tableHeadersTooltips = { "", "Press <enter> to set token value.", "The graph element where the token is declared.", "Node inputs which reference the token." };
drawTableHeadersRowWithTooltips(tableHeadersTooltips);

ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(&token);
for (const auto& [tokenName, tokenPtr] : currTokenMap)
{
ImGui::TableNextRow(); // Start new row
ImGui::PushID(&tokenName);

ImGui::Text("%s", token.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", value.c_str());
// Name
ImGui::TableNextColumn();
ImGui::Text("%s", tokenName.c_str());

ImGui::PopID();
// Value
ImGui::TableNextColumn();
std::string tokenValue = tokenPtr->getValue();

if (ImGui::InputText("##token_value", &tokenValue, ImGuiInputTextFlags_EnterReturnsTrue))
{
tokenPtr->setValue(tokenValue); // Write out new token value
updateMaterials(); // Trigger update of material
}

ImGui::EndTable();
ImGui::SetWindowFontScale(1.0f);
// Source Element
ImGui::TableNextColumn();
ImGui::Text("%s", tokenPtr->getSourceElementString().c_str());

// Affected Inputs
ImGui::TableNextColumn();
ImGui::Text("%s", tokenPtr->getAffectedInputsString().c_str());

ImGui::PopID();
}

ImGui::EndTable();
ImGui::SetWindowFontScale(1.0f); // Restore font scale
}
}

Expand Down Expand Up @@ -3875,6 +3898,20 @@ void Graph::showHelp() const
ImGui::BulletText("\"Node Info\" Will toggle showing node information.");
}
}
void Graph::drawHelpMarker(const char* content)
{
constexpr float WRAP_POSITION = 32.f; // Compile-time definition of text-wrap position

ImGui::TextDisabled(HELP_MARKER_TEXT); // Draw help marker
if (!ImGui::IsItemHovered())
return; // If help marker isn't hovered return early

ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * WRAP_POSITION);
ImGui::TextUnformatted(content);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}

void Graph::addNodePopup(bool cursor)
{
Expand Down
30 changes: 29 additions & 1 deletion source/MaterialXGraphEditor/Graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,35 @@ class Graph

void showHelp() const;

// A compile-time constant member variable that corresponds to the function below. Defined in header as visibility is desirable here.
static constexpr char HELP_MARKER_TEXT[] = "(?)";
// Static helper function to draw a marker via ImGui which shows a tooltip when hovered
static void drawHelpMarker(const char* content);

// Static helper function to display tooltips for headers in an ImGui table
template <std::size_t N> static void drawTableHeadersRowWithTooltips(const std::array<const char*, N>& tooltips)
{
const int columnCount = ImGui::TableGetColumnCount();
if (columnCount == 0 || columnCount != N)
return; // Given array size should match number of columns in table

ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
for (int col = 0; col < columnCount; ++col)
{
if (!ImGui::TableSetColumnIndex(col))
continue; // Do not draw if column is not visible
ImGui::TableHeader(ImGui::TableGetColumnName(col)); // Header name

std::string colTooltip = tooltips[col];
if (!colTooltip.empty() && ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::TextUnformatted(tooltips[col]);
ImGui::EndTooltip();
}
}
}

private:
mx::StringVec _geomFilter;
mx::StringVec _mtlxFilter;
Expand Down Expand Up @@ -389,5 +418,4 @@ class Graph
// Current height of the diagnostic panel; adjusted by the resize handle.
float _diagPanelHeight = 120.f;
};

#endif
70 changes: 70 additions & 0 deletions source/MaterialXGraphEditor/UiNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,76 @@ mx::NodeGraphPtr UiNode::getNodeGraph() const
return _element ? _element->asA<mx::NodeGraph>() : nullptr;
}

void UiNode::buildUiTokenMap()
{
_uiTokenMap.clear(); // Assume we want clean slate

const mx::NodePtr node = getNode();
if (!node) return; // Early return as no further work is needed

mx::ElementPtr currElem = node->asA<mx::Element>(); // Explicit upcast
// Traverse upward using parent pointers to collect all active tokens in the current scope
while (currElem)
{
if (mx::ConstInterfaceElementPtr currInterfaceElem = currElem->asA<mx::InterfaceElement>())
{
UiToken::applyTokenMapping(&_uiTokenMap, currInterfaceElem, currElem);

// If the node is a nodegraph, check for tokens on corresponding nodedef
if (mx::ConstNodeGraphPtr currNodeGraph = currElem->asA<mx::NodeGraph>())
{
if (mx::NodeDefPtr currNodedef = currNodeGraph->getNodeDef())
{
UiToken::applyTokenMapping(&_uiTokenMap, currNodedef, currNodedef);
}
}

// If the node is a custom node instance, check for tokens on corresponding nodedef
if (mx::NodePtr currNode = currElem->asA<mx::Node>())
{
if (mx::NodeDefPtr currNodedef = currNode->getNodeDef())
{
UiToken::applyTokenMapping(&_uiTokenMap, currNodedef, currNodedef);
}
}
}
currElem = currElem->getParent();
}

// Traverse through inputs and determine which tokens their value depends on
for (const auto& input : node->getActiveInputs())
{
if (input->getType() != "filename")
continue;

mx::StringResolverPtr inputResolver = input->createStringResolver();
const mx::StringMap& inputTokens = inputResolver->getFilenameSubstitutions();

mx::StringMap inputTokensRenormalized;
for (const auto& entry : inputTokens)
{
// Store tokens without excess delimiters
inputTokensRenormalized[entry.first] = entry.first.substr(1, entry.first.size() - 2);
}

std::string inputValue = input->getValueString();
if (inputValue.empty() && input->hasInterfaceName())
inputValue = input->getInterfaceInput()->getValueString(); // Get value from referenced interface

for (const auto& entry : inputTokens)
{
if (inputValue.find(entry.first) != std::string::npos)
{
// Ensure that the token exists in the pre-built map
if (auto it = _uiTokenMap.find(inputTokensRenormalized[entry.first]); it != _uiTokenMap.end())
{
it->second->addAffectedInput(input); // Append to affected inputs of corresponding entry in token map
}
}
}
}
}

// return the uiNode connected with input name
UiNodePtr UiNode::getConnectedNode(const std::string& name)
{
Expand Down
Loading
Loading