Skip to content

Commit

Permalink
Update link connections by dragging between ports (#1569)
Browse files Browse the repository at this point in the history
Currently, one must explicitly delete an existing link before adding a new link for a given node. This change enables dragging to update a link connection, and automatically deletes the existing link on the input pin if one exists.

Note: An output pin on a node may have many outgoing links, but an input pin may only have one incoming link. This behaviour is unchanged.

Also:

- Renames a few variables for consistency on start/end and input/output pins
- Ensures you can't add a link from an input to an input, or from an output to an output
- Can also drag from an output to an input and have it correctly delete existing link
  • Loading branch information
erikaharrison-adsk authored Oct 13, 2023
1 parent 54adaee commit eb52ec2
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 140 deletions.
307 changes: 168 additions & 139 deletions source/MaterialXGraphEditor/Graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ void Graph::setRenderMaterial(UiNodePtr node)
}
}

void Graph::updateMaterials(mx::InputPtr input, mx::ValuePtr value)
void Graph::updateMaterials(mx::InputPtr input /* = nullptr */, mx::ValuePtr value /* = nullptr */)
{
std::string renderablePath;
if (_currRenderNode)
Expand Down Expand Up @@ -2483,12 +2483,22 @@ void Graph::setDefaults(mx::InputPtr input)
}
}

void Graph::addLink(ed::PinId inputPinId, ed::PinId outputPinId)
void Graph::addLink(ed::PinId startPinId, ed::PinId endPinId)
{
int end_attr = int(outputPinId.Get());
int start_attr = int(inputPinId.Get());
UiPinPtr inputPin = getPin(outputPinId);
UiPinPtr outputPin = getPin(inputPinId);
// prefer to assume left to right - start is an output, end is an input; swap if inaccurate
if (UiPinPtr inputPin = getPin(endPinId); inputPin && inputPin->_kind != ed::PinKind::Input)
{
auto tmp = startPinId;
startPinId = endPinId;
endPinId = tmp;
}

int end_attr = int(endPinId.Get());
int start_attr = int(startPinId.Get());
ed::PinId outputPinId = startPinId;
ed::PinId inputPinId = endPinId;
UiPinPtr outputPin = getPin(outputPinId);
UiPinPtr inputPin = getPin(inputPinId);

if (!inputPin || !outputPin)
{
Expand All @@ -2505,187 +2515,206 @@ void Graph::addLink(ed::PinId inputPinId, ed::PinId outputPinId)
return;
}

if (inputPin->_connected == false)
// Perform kind check
bool kindsMatch = (outputPin->_kind == inputPin->_kind);
if (kindsMatch)
{
int upNode = getNodeId(inputPinId);
int downNode = getNodeId(outputPinId);
UiNodePtr uiDownNode = _graphNodes[downNode];
UiNodePtr uiUpNode = _graphNodes[upNode];
if (!uiDownNode || !uiUpNode)
{
ed::RejectNewItem();
return;
}
ed::RejectNewItem();
showLabel("Invalid connection due to same input/output kind", ImColor(50, 50, 50, 255));
return;
}

// make sure there is an implementation for node
const mx::ShaderGenerator& shadergen = _renderer->getGenContext().getShaderGenerator();
int upNode = getNodeId(outputPinId);
int downNode = getNodeId(inputPinId);
UiNodePtr uiDownNode = _graphNodes[downNode];
UiNodePtr uiUpNode = _graphNodes[upNode];
if (!uiDownNode || !uiUpNode)
{
ed::RejectNewItem();
return;
}

// Prevent direct connecting from input to output
if (uiDownNode->getInput() && uiUpNode->getOutput())
{
ed::RejectNewItem();
showLabel("Direct connections between inputs and outputs is invalid", ImColor(50, 50, 50, 255));
return;
}
// make sure there is an implementation for node
const mx::ShaderGenerator& shadergen = _renderer->getGenContext().getShaderGenerator();

// Find the implementation for this nodedef if not an input or output uinode
if (uiDownNode->getInput() && _isNodeGraph)
// Prevent direct connecting from input to output
if (uiDownNode->getInput() && uiUpNode->getOutput())
{
ed::RejectNewItem();
showLabel("Direct connections between inputs and outputs is invalid", ImColor(50, 50, 50, 255));
return;
}

// Find the implementation for this nodedef if not an input or output uinode
if (uiDownNode->getInput() && _isNodeGraph)
{
ed::RejectNewItem();
showLabel("Cannot connect to inputs inside of graph", ImColor(50, 50, 50, 255));
return;
}
else if (uiUpNode->getNode())
{
mx::ShaderNodeImplPtr impl = shadergen.getImplementation(*_graphNodes[upNode]->getNode()->getNodeDef(), _renderer->getGenContext());
if (!impl)
{
ed::RejectNewItem();
showLabel("Cannot connect to inputs inside of graph", ImColor(50, 50, 50, 255));
showLabel("Invalid Connection: Node does not have an implementation", ImColor(50, 50, 50, 255));
return;
}
else if (uiUpNode->getNode())
}

if (ed::AcceptNewItem())
{
// If the accepting node already has a link, remove it
if (inputPin->_connected)
{
mx::ShaderNodeImplPtr impl = shadergen.getImplementation(*_graphNodes[upNode]->getNode()->getNodeDef(), _renderer->getGenContext());
if (!impl)
for (auto linksItr = _currLinks.begin(); linksItr != _currLinks.end(); linksItr++)
{
ed::RejectNewItem();
showLabel("Invalid Connection: Node does not have an implementation", ImColor(50, 50, 50, 255));
return;
if (linksItr->_endAttr == end_attr)
{
// found existing link - remove it; adapted from deleteLink
// note: ed::BreakLinks doesn't work as the order ends up inaccurate
deleteLinkInfo(linksItr->_startAttr, linksItr->_endAttr);
_currLinks.erase(linksItr);
break;
}
}

}

if (ed::AcceptNewItem())
{
// Since we accepted new link, lets add one to our list of links.
Link link;
link._startAttr = start_attr;
link._endAttr = end_attr;
_currLinks.push_back(link);
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
// Since we accepted new link, lets add one to our list of links.
Link link;
link._startAttr = start_attr;
link._endAttr = end_attr;
_currLinks.push_back(link);
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);

if (uiDownNode->getNode() || uiDownNode->getNodeGraph())
if (uiDownNode->getNode() || uiDownNode->getNodeGraph())
{
mx::InputPtr connectingInput = nullptr;
for (UiPinPtr pin : uiDownNode->inputPins)
{
mx::InputPtr connectingInput = nullptr;
for (UiPinPtr pin : uiDownNode->inputPins)
if (pin->_pinId == inputPinId)
{
if (pin->_pinId == outputPinId)
addNodeInput(uiDownNode, pin->_input);
// update value to be empty
if (uiDownNode->getNode() && uiDownNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
{
addNodeInput(uiDownNode, pin->_input);
// update value to be empty
if (uiDownNode->getNode() && uiDownNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
if (uiUpNode->getOutput() != nullptr)
{
if (uiUpNode->getOutput() != nullptr)
{
pin->_input->setConnectedOutput(uiUpNode->getOutput());
}
else if (uiUpNode->getInput() != nullptr)
{
pin->_input->setInterfaceName(uiUpNode->getName());
}
else
pin->_input->setConnectedOutput(uiUpNode->getOutput());
}
else if (uiUpNode->getInput() != nullptr)
{
pin->_input->setInterfaceName(uiUpNode->getName());
}
else
{
// node graph
if (uiUpNode->getNodeGraph() != nullptr)
{
// node graph
if (uiUpNode->getNodeGraph() != nullptr)
for (UiPinPtr outPin : uiUpNode->outputPins)
{
for (UiPinPtr outPin : uiUpNode->outputPins)
// set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
// set pin connection to correct output
if (outPin->_pinId == inputPinId)
{
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
}
else
{
pin->_input->setConnectedNode(uiUpNode->getNode());
}
}
else
{
pin->_input->setConnectedNode(uiUpNode->getNode());
}
}
}
else
{
if (uiUpNode->getInput())
{
pin->_input->setInterfaceName(uiUpNode->getName());
}
else
{
if (uiUpNode->getInput())
{
pin->_input->setInterfaceName(uiUpNode->getName());
}
else
if (uiUpNode->getNode())
{
if (uiUpNode->getNode())
mx::NodePtr upstreamNode = _graphNodes[upNode]->getNode();
mx::NodeDefPtr upstreamNodeDef = upstreamNode->getNodeDef();
bool isMultiOutput = upstreamNodeDef ? upstreamNodeDef->getOutputs().size() > 1 : false;

// This is purely to avoid adding a reference to an update node only 1 output,
// as currently validation consides adding this an error. Otherwise
// it will add an "output" attribute all the time.
if (!isMultiOutput)
{
mx::NodePtr upstreamNode = _graphNodes[upNode]->getNode();
mx::NodeDefPtr upstreamNodeDef = upstreamNode->getNodeDef();
bool isMultiOutput = upstreamNodeDef ? upstreamNodeDef->getOutputs().size() > 1 : false;

// This is purely to avoid adding a reference to an update node only 1 output,
// as currently validation consides adding this an error. Otherwise
// it will add an "output" attribute all the time.
if (!isMultiOutput)
{
pin->_input->setConnectedNode(uiUpNode->getNode());
}
else
pin->_input->setConnectedNode(uiUpNode->getNode());
}
else
{
for (UiPinPtr outPin : _graphNodes[upNode]->outputPins)
{
for (UiPinPtr outPin : _graphNodes[upNode]->outputPins)
// set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
// set pin connection to correct output
if (outPin->_pinId == inputPinId)
mx::OutputPtr outputs = uiUpNode->getNode()->getOutput(outPin->_name);
if (!outputs)
{
mx::OutputPtr outputs = uiUpNode->getNode()->getOutput(outPin->_name);
if (!outputs)
{
outputs = uiUpNode->getNode()->addOutput(outPin->_name, pin->_input->getType());
}
pin->_input->setConnectedOutput(outputs);
outputs = uiUpNode->getNode()->addOutput(outPin->_name, pin->_input->getType());
}
pin->_input->setConnectedOutput(outputs);
}
}
}
else if (uiUpNode->getNodeGraph())
}
else if (uiUpNode->getNodeGraph())
{
for (UiPinPtr outPin : uiUpNode->outputPins)
{
for (UiPinPtr outPin : uiUpNode->outputPins)
// set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
// set pin connection to correct output
if (outPin->_pinId == inputPinId)
{
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
}
}
}

pin->setConnected(true);
pin->_input->removeAttribute(mx::ValueElement::VALUE_ATTRIBUTE);
connectingInput = pin->_input;
break;
}

pin->setConnected(true);
pin->_input->removeAttribute(mx::ValueElement::VALUE_ATTRIBUTE);
connectingInput = pin->_input;
break;
}
// create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else if (_graphNodes[downNode]->getOutput() != nullptr)
{
mx::InputPtr connectingInput = nullptr;
_graphNodes[downNode]->getOutput()->setConnectedNode(_graphNodes[upNode]->getNode());
// create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else if (_graphNodes[downNode]->getOutput() != nullptr)
{
mx::InputPtr connectingInput = nullptr;
_graphNodes[downNode]->getOutput()->setConnectedNode(_graphNodes[upNode]->getNode());

// create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else
// create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else
{
// create new edge and set edge info
UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], nullptr);
if (!edgeExists(newEdge))
{
// create new edge and set edge info
UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], nullptr);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_currEdge.push_back(newEdge);
_graphNodes[downNode]->edges.push_back(newEdge);
_currEdge.push_back(newEdge);

// update input node num and output connections
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]);
}
// update input node num and output connections
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]);
}
}
}
else
{
ed::RejectNewItem();
}
}

void Graph::removeEdge(int downNode, int upNode, UiPinPtr pin)
Expand Down Expand Up @@ -4045,12 +4074,12 @@ void Graph::drawGraph(ImVec2 mousePos)
// Add new link
if (ed::BeginCreate())
{
ed::PinId inputPinId, outputPinId, filterPinId;
if (ed::QueryNewLink(&inputPinId, &outputPinId))
ed::PinId startPinId, endPinId, filterPinId;
if (ed::QueryNewLink(&startPinId, &endPinId))
{
if (!readOnly())
{
addLink(inputPinId, outputPinId);
addLink(startPinId, endPinId);
}
else
{
Expand Down
Loading

0 comments on commit eb52ec2

Please sign in to comment.