// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include #include #include #include namespace { // Based on the dimensions of the dot_color3 node, computed by calling ed::getNodeSize const ImVec2 DEFAULT_NODE_SIZE = ImVec2(138, 116); const int DEFAULT_ALPHA = 255; const int FILTER_ALPHA = 50; const std::array NODE_GROUP_ORDER = { "texture2d", "texture3d", "procedural", "procedural2d", "procedural3d", "geometric", "translation", "convolution2d", "math", "adjustment", "compositing", "conditional", "channel", "organization", "global", "application", "material", "shader", "pbr", "light", "colortransform", "none" }; // Based on ImRect_Expanded function in ImGui Node Editor blueprints-example.cpp ImRect expandImRect(const ImRect& rect, float x, float y) { ImRect result = rect; result.Min.x -= x; result.Min.y -= y; result.Max.x += x; result.Max.y += y; return result; } // Based on the splitter function in the ImGui Node Editor blueprints-example.cpp static bool splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f) { using namespace ImGui; ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiID id = window->GetID("##Splitter"); ImRect bb; bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f); } // Based on showLabel from ImGui Node Editor blueprints-example.cpp auto showLabel = [](const char* label, ImColor color) { ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetTextLineHeight()); auto size = ImGui::CalcTextSize(label); auto padding = ImGui::GetStyle().FramePadding; auto spacing = ImGui::GetStyle().ItemSpacing; ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(spacing.x, -spacing.y)); auto rectMin = ImGui::GetCursorScreenPos() - padding; auto rectMax = ImGui::GetCursorScreenPos() + size + padding; auto drawList = ImGui::GetWindowDrawList(); drawList->AddRectFilled(rectMin, rectMax, color, size.y * 0.15f); ImGui::TextUnformatted(label); }; // Create a more user-friendly node definition name std::string getUserNodeDefName(const std::string& val) { const std::string ND_PREFIX = "ND_"; std::string result = val; if (mx::stringStartsWith(val, ND_PREFIX)) { result = val.substr(3, val.length()); } return result; } } // anonymous namespace // // Link methods // Link::Link() : _startAttr(-1), _endAttr(-1) { static int nextId = 1; _id = nextId++; } // // Graph methods // Graph::Graph(const std::string& materialFilename, const std::string& meshFilename, const mx::FileSearchPath& searchPath, const mx::FilePathVec& libraryFolders, int viewWidth, int viewHeight) : _materialFilename(materialFilename), _searchPath(searchPath), _libraryFolders(libraryFolders), _initial(false), _delete(false), _fileDialogSave(FileDialog::EnterNewFilename), _isNodeGraph(false), _graphTotalSize(0), _popup(false), _shaderPopup(false), _searchNodeId(-1), _addNewNode(false), _ctrlClick(false), _isCut(false), _autoLayout(false), _frameCount(INT_MIN), _fontScale(1.0f), _saveNodePositions(true) { loadStandardLibraries(); setPinColor(); // Set up filters load and save _mtlxFilter.push_back(".mtlx"); _geomFilter.push_back(".obj"); _geomFilter.push_back(".glb"); _geomFilter.push_back(".gltf"); _graphDoc = loadDocument(materialFilename); _initial = true; createNodeUIList(_stdLib); if (_graphDoc) { buildUiBaseGraph(_graphDoc); _currGraphElem = _graphDoc; _prevUiNode = nullptr; } // Create a renderer using the initial startup document. mx::FilePath captureFilename = "resources/Materials/Examples/example.png"; std::string envRadianceFilename = "resources/Lights/san_giuseppe_bridge_split.hdr"; _renderer = std::make_shared(_graphDoc, _stdLib, meshFilename, envRadianceFilename, _searchPath, viewWidth, viewHeight); _renderer->initialize(); for (const std::string& ext : _renderer->getImageHandler()->supportedExtensions()) { _imageFilter.push_back("." + ext); } _renderer->updateMaterials(nullptr); for (const std::string& incl : _renderer->getXincludeFiles()) { _xincludeFiles.insert(incl); } } mx::ElementPredicate Graph::getElementPredicate() const { return [this](mx::ConstElementPtr elem) { if (elem->hasSourceUri()) { return (_xincludeFiles.count(elem->getSourceUri()) == 0); } return true; }; } void Graph::loadStandardLibraries() { // Initialize the standard library. try { _stdLib = mx::createDocument(); _xincludeFiles = mx::loadLibraries(_libraryFolders, _searchPath, _stdLib); if (_xincludeFiles.empty()) { std::cerr << "Could not find standard data libraries on the given search path: " << _searchPath.asString() << std::endl; } } catch (std::exception& e) { std::cerr << "Failed to load standard data libraries: " << e.what() << std::endl; return; } } mx::DocumentPtr Graph::loadDocument(const mx::FilePath& filename) { mx::FilePathVec libraryFolders = { "libraries" }; _libraryFolders = libraryFolders; mx::XmlReadOptions readOptions; readOptions.readXIncludeFunction = [](mx::DocumentPtr doc, const mx::FilePath& filename, const mx::FileSearchPath& searchPath, const mx::XmlReadOptions* options) { mx::FilePath resolvedFilename = searchPath.find(filename); if (resolvedFilename.exists()) { try { readFromXmlFile(doc, resolvedFilename, searchPath, options); } catch (mx::Exception& e) { std::cerr << "Failed to read include file: " << filename.asString() << ". " << std::string(e.what()) << std::endl; } } else { std::cerr << "Include file not found: " << filename.asString() << std::endl; } }; mx::DocumentPtr doc = mx::createDocument(); try { if (!filename.isEmpty()) { mx::readFromXmlFile(doc, filename, _searchPath, &readOptions); doc->setDataLibrary(_stdLib); std::string message; if (!doc->validate(&message)) { std::cerr << "*** Validation warnings for " << filename.asString() << " ***" << std::endl; std::cerr << message << std::endl; } // Cache the currently loaded file _materialFilename = filename; } } catch (mx::Exception& e) { std::cerr << "Failed to read file: " << filename.asString() << ": \"" << std::string(e.what()) << "\"" << std::endl; } _graphStack = std::stack>(); _pinStack = std::stack>(); return doc; } void Graph::addExtraNodes() { if (!_graphDoc) { return; } // Get all types from the doc std::vector types; std::vector typeDefs = _graphDoc->getTypeDefs(); types.reserve(typeDefs.size()); for (auto typeDef : typeDefs) { types.push_back(typeDef->getName()); } // Add input and output nodes for all types for (const std::string& type : types) { std::string nodeName = "ND_input_" + type; _nodesToAdd.emplace_back(nodeName, type, "input", "Input Nodes"); nodeName = "ND_output_" + type; _nodesToAdd.emplace_back(nodeName, type, "output", "Output Nodes"); } // Add group node _nodesToAdd.emplace_back("ND_group", "", "group", "Group Nodes"); // Add nodegraph node _nodesToAdd.emplace_back("ND_nodegraph", "", "nodegraph", "Node Graph"); } ed::PinId Graph::getOutputPin(UiNodePtr node, UiNodePtr upNode, UiPinPtr input) { if (upNode->getNodeGraph() != nullptr) { // For nodegraph need to get the correct output pin according to the names of the output nodes mx::OutputPtr output; if (input->_pinNode->getNode()) { output = input->_pinNode->getNode()->getConnectedOutput(input->_name); } else if (input->_pinNode->getNodeGraph()) { output = input->_pinNode->getNodeGraph()->getConnectedOutput(input->_name); } if (output) { std::string outName = output->getName(); for (UiPinPtr outputs : upNode->outputPins) { if (outputs->_name == outName) { return outputs->_pinId; } } } return ed::PinId(); } else { // For node need to get the correct output pin based on the output attribute if (!upNode->outputPins.empty()) { std::string outputName = mx::EMPTY_STRING; if (input->_input) { outputName = input->_input->getOutputString(); } else if (input->_output) { outputName = input->_output->getOutputString(); } size_t pinIndex = 0; if (!outputName.empty()) { for (size_t i = 0; i < upNode->outputPins.size(); i++) { if (upNode->outputPins[i]->_name == outputName) { pinIndex = i; break; } } } return (upNode->outputPins[pinIndex]->_pinId); } return ed::PinId(); } } void Graph::linkGraph() { _currLinks.clear(); // Start with bottom of graph for (UiNodePtr node : _graphNodes) { std::vector inputs = node->inputPins; if (node->getInput() == nullptr) { for (size_t i = 0; i < inputs.size(); i++) { // Get upstream node for all inputs std::string inputName = inputs[i]->_name; UiNodePtr inputNode = node->getConnectedNode(inputName); if (inputNode != nullptr) { Link link; // Get the input connections for the current UiNode ax::NodeEditor::PinId id = inputs[i]->_pinId; inputs[i]->setConnected(true); int end = int(id.Get()); link._endAttr = end; // Get id number of output of node ed::PinId outputId = getOutputPin(node, inputNode, inputs[i]); int start = int(outputId.Get()); if (start >= 0) { // Connect the correct output pin to this input for (UiPinPtr outPin : inputNode->outputPins) { if (outPin->_pinId == outputId) { outPin->setConnected(true); outPin->addConnection(inputs[i]); } } link._startAttr = start; if (!linkExists(link)) { _currLinks.push_back(link); } } } else if (inputs[i]->_input) { if (inputs[i]->_input->getInterfaceInput()) { inputs[i]->setConnected(true); } } else { inputs[i]->setConnected(false); } } } } } void Graph::connectLinks() { for (Link const& link : _currLinks) { ed::Link(link._id, link._startAttr, link._endAttr); } } int Graph::findLinkPosition(int id) { int count = 0; for (size_t i = 0; i < _currLinks.size(); i++) { if (_currLinks[i]._id == id) { return count; } count++; } return -1; } bool Graph::checkPosition(UiNodePtr node) { mx::ElementPtr elem = node->getElement(); return elem && !elem->getAttribute(mx::Element::XPOS_ATTRIBUTE).empty(); } // Calculate the total vertical space the node level takes up float Graph::totalHeight(int level) { float total = 0.f; for (UiNodePtr node : _levelMap[level]) { total += ed::GetNodeSize(node->getId()).y; } return total; } // Set the y-position of node based on the starting position and the nodes above it void Graph::setYSpacing(int level, float startingPos) { // set the y spacing for each node float currPos = startingPos; for (UiNodePtr node : _levelMap[level]) { ImVec2 oldPos = ed::GetNodePosition(node->getId()); ed::SetNodePosition(node->getId(), ImVec2(oldPos.x, currPos)); currPos += ed::GetNodeSize(node->getId()).y + 40; } } // Calculate the average y-position for a specific node level float Graph::findAvgY(const std::vector& nodes) { // find the mid point of node level grou[ float total = 0.f; int count = 0; for (UiNodePtr node : nodes) { ImVec2 pos = ed::GetNodePosition(node->getId()); ImVec2 size = ed::GetNodeSize(node->getId()); total += ((size.y + pos.y) + pos.y) / 2; count++; } return (total / count); } void Graph::findYSpacing(float startY) { // Assume level 0 is set // For each level find the average y position of the previous level to use as a spacing guide int i = 0; for (std::pair> levelChunk : _levelMap) { if (_levelMap[i].size() > 0) { if (_levelMap[i][0]->_level > 0) { int prevLevel = _levelMap[i].front()->_level - 1; float avgY = findAvgY(_levelMap[prevLevel]); float height = totalHeight(_levelMap[i].front()->_level); // calculate the starting position to be above the previous level's center so that it is evenly spaced on either side of the center float startingPos = avgY - ((height + (_levelMap[i].size() * 20)) / 2) + startY; setYSpacing(_levelMap[i].front()->_level, startingPos); } else { setYSpacing(_levelMap[i].front()->_level, startY); } } ++i; } } ImVec2 Graph::layoutPosition(UiNodePtr layoutNode, ImVec2 startingPos, bool initialLayout, int level) { if (checkPosition(layoutNode) && !_autoLayout) { for (UiNodePtr node : _graphNodes) { // Since nodegraph nodes do not have MaterialX info they are placed based on their connected node if (node->getNodeGraph() != nullptr) { std::vector outputCon = node->getOutputConnections(); if (outputCon.size() > 0) { ImVec2 outputPos = ed::GetNodePosition(outputCon[0]->getId()); ed::SetNodePosition(node->getId(), ImVec2(outputPos.x - 400, outputPos.y)); node->setPos(ImVec2(outputPos.x - 400, outputPos.y)); } } else { // Don't set position of group nodes if (node->getMessage().empty()) { mx::ElementPtr elem = node->getElement(); if (elem && elem->hasAttribute(mx::Element::XPOS_ATTRIBUTE)) { float x = std::stof(elem->getAttribute(mx::Element::XPOS_ATTRIBUTE)); if (elem->hasAttribute(mx::Element::YPOS_ATTRIBUTE)) { float y = std::stof(elem->getAttribute(mx::Element::YPOS_ATTRIBUTE)); x *= DEFAULT_NODE_SIZE.x; y *= DEFAULT_NODE_SIZE.y; ed::SetNodePosition(node->getId(), ImVec2(x, y)); node->setPos(ImVec2(x, y)); } } } } } return ImVec2(0.f, 0.f); } else { ImVec2 currPos = startingPos; ImVec2 newPos = currPos; if (layoutNode->_level != -1) { if (layoutNode->_level < level) { // Remove the old instance of the node from the map int levelNum = 0; int removeNum = -1; for (UiNodePtr levelNode : _levelMap[layoutNode->_level]) { if (levelNode->getName() == layoutNode->getName()) { removeNum = levelNum; } levelNum++; } if (removeNum > -1) { _levelMap[layoutNode->_level].erase(_levelMap[layoutNode->_level].begin() + removeNum); } layoutNode->_level = level; } } else { layoutNode->_level = level; } auto it = _levelMap.find(layoutNode->_level); if (it != _levelMap.end()) { // Key already exists so add to it bool nodeFound = false; for (UiNodePtr node : it->second) { if (node && node->getName() == layoutNode->getName()) { nodeFound = true; break; } } if (!nodeFound) { _levelMap[layoutNode->_level].push_back(layoutNode); } } else { // Insert new vector into key std::vector newValue = { layoutNode }; _levelMap.insert({ layoutNode->_level, newValue }); } std::vector pins = layoutNode->inputPins; if (initialLayout) { // Check number of inputs that are connected to node if (layoutNode->getInputConnect() > 0) { // Not top of node graph so stop recursion if (pins.size() != 0 && layoutNode->getInput() == nullptr) { for (size_t i = 0; i < pins.size(); i++) { // Get upstream node for all inputs newPos = startingPos; UiNodePtr nextNode = layoutNode->getConnectedNode(pins[i]->_name); if (nextNode) { startingPos.x = (1200.f - ((layoutNode->_level) * 250)) * _fontScale; ed::SetNodePosition(layoutNode->getId(), startingPos); layoutNode->setPos(ImVec2(startingPos)); // Call layout position on upstream node with newPos to the left of current node layoutPosition(nextNode, ImVec2(newPos.x, startingPos.y), initialLayout, layoutNode->_level + 1); } } } } else { startingPos.x = (1200.f - ((layoutNode->_level) * 250)) * _fontScale; layoutNode->setPos(ImVec2(startingPos)); // Set current node position ed::SetNodePosition(layoutNode->getId(), ImVec2(startingPos)); } } return ImVec2(0.f, 0.f); } } void Graph::layoutInputs() { // Layout inputs after other nodes so that they can be all in a line on far left side of node graph if (_levelMap.begin() != _levelMap.end()) { int levelCount = -1; for (std::pair> nodes : _levelMap) { ++levelCount; } ImVec2 startingPos = ed::GetNodePosition(_levelMap[levelCount].back()->getId()); startingPos.y += ed::GetNodeSize(_levelMap[levelCount].back()->getId()).y + 20; for (UiNodePtr uiNode : _graphNodes) { if (uiNode->getOutputConnections().size() == 0 && (uiNode->getInput() != nullptr)) { ed::SetNodePosition(uiNode->getId(), ImVec2(startingPos)); startingPos.y += ed::GetNodeSize(uiNode->getId()).y; startingPos.y += 23; } else if (uiNode->getOutputConnections().size() == 0 && (uiNode->getNode() != nullptr)) { if (uiNode->getNode()->getCategory() != mx::SURFACE_MATERIAL_NODE_STRING) { layoutPosition(uiNode, ImVec2(1200, 750), _initial, 0); } } } } } void Graph::setPinColor() { _pinColor.emplace("integer", ImColor(255, 255, 28, 255)); _pinColor.emplace("boolean", ImColor(255, 0, 255, 255)); _pinColor.emplace("float", ImColor(50, 100, 255, 255)); _pinColor.emplace("color3", ImColor(178, 34, 34, 255)); _pinColor.emplace("color4", ImColor(50, 10, 255, 255)); _pinColor.emplace("vector2", ImColor(100, 255, 100, 255)); _pinColor.emplace("vector3", ImColor(0, 255, 0, 255)); _pinColor.emplace("vector4", ImColor(100, 0, 100, 255)); _pinColor.emplace("matrix33", ImColor(0, 100, 100, 255)); _pinColor.emplace("matrix44", ImColor(50, 255, 100, 255)); _pinColor.emplace("filename", ImColor(255, 184, 28, 255)); _pinColor.emplace("string", ImColor(100, 100, 50, 255)); _pinColor.emplace("geomname", ImColor(121, 60, 180, 255)); _pinColor.emplace("BSDF", ImColor(10, 181, 150, 255)); _pinColor.emplace("EDF", ImColor(255, 50, 100, 255)); _pinColor.emplace("VDF", ImColor(0, 100, 151, 255)); _pinColor.emplace(mx::SURFACE_SHADER_TYPE_STRING, ImColor(150, 255, 255, 255)); _pinColor.emplace(mx::MATERIAL_TYPE_STRING, ImColor(255, 255, 255, 255)); _pinColor.emplace(mx::DISPLACEMENT_SHADER_TYPE_STRING, ImColor(155, 50, 100, 255)); _pinColor.emplace(mx::VOLUME_SHADER_TYPE_STRING, ImColor(155, 250, 100, 255)); _pinColor.emplace(mx::LIGHT_SHADER_TYPE_STRING, ImColor(100, 150, 100, 255)); _pinColor.emplace("none", ImColor(140, 70, 70, 255)); _pinColor.emplace(mx::MULTI_OUTPUT_TYPE_STRING, ImColor(70, 70, 70, 255)); _pinColor.emplace("integerarray", ImColor(200, 10, 100, 255)); _pinColor.emplace("floatarray", ImColor(25, 250, 100)); _pinColor.emplace("color3array", ImColor(25, 200, 110)); _pinColor.emplace("color4array", ImColor(50, 240, 110)); _pinColor.emplace("vector2array", ImColor(50, 200, 75)); _pinColor.emplace("vector3array", ImColor(20, 200, 100)); _pinColor.emplace("vector4array", ImColor(100, 200, 100)); _pinColor.emplace("geomnamearray", ImColor(150, 200, 100)); _pinColor.emplace("stringarray", ImColor(120, 180, 100)); } void Graph::setRenderMaterial(UiNodePtr node) { // For now only surface shaders and materials are considered renderable. // This can be adjusted as desired to include being able to use outputs, // and / a sub-graph in the nodegraph. const mx::StringSet RENDERABLE_TYPES = { mx::MATERIAL_TYPE_STRING, mx::SURFACE_SHADER_TYPE_STRING }; // Set render node right away is node is renderable if (node->getNode() && RENDERABLE_TYPES.count(node->getNode()->getType())) { // Only set new render node if different material has been selected if (_currRenderNode != node) { _currRenderNode = node; _frameCount = ImGui::GetFrameCount(); _renderer->setMaterialCompilation(true); } } // Traverse downstream looking for the first renderable element. else { mx::NodePtr mtlxNode = node->getNode(); mx::NodeGraphPtr mtlxNodeGraph = node->getNodeGraph(); mx::OutputPtr mtlxOutput = node->getOutput(); if (mtlxOutput) { mx::ElementPtr parent = mtlxOutput->getParent(); if (parent->isA()) mtlxNodeGraph = parent->asA(); else if (parent->isA()) mtlxNode = parent->asA(); } mx::StringSet testPaths; if (mtlxNode) { mx::ElementPtr parent = mtlxNode->getParent(); if (parent->isA()) { // There is no logic to support traversing from inside a functional graph // to it's instance and hence downstream so skip this from consideration. // The closest approach would be to "flatten" all definitions to compound graphs. mx::NodeGraphPtr parentGraph = parent->asA(); if (parentGraph->getNodeDef()) { return; } } testPaths.insert(mtlxNode->getNamePath()); } else if (mtlxNodeGraph) { testPaths.insert(mtlxNodeGraph->getNamePath()); } mx::NodePtr foundNode = nullptr; while (!testPaths.empty() && !foundNode) { mx::StringSet nextPaths; for (const std::string& testPath : testPaths) { mx::ElementPtr testElem = _graphDoc->getDescendant(testPath); mx::NodePtr testNode = testElem->asA(); std::vector downstreamPorts; if (testNode) { downstreamPorts = testNode->getDownstreamPorts(); } else { mx::NodeGraphPtr testGraph = testElem->asA(); if (testGraph) { downstreamPorts = testGraph->getDownstreamPorts(); } } // Test all downstream ports. If the port's node is renderable // then stop searching. for (mx::PortElementPtr downstreamPort : downstreamPorts) { mx::ElementPtr parent = downstreamPort->getParent(); if (parent) { mx::NodePtr downstreamNode = parent->asA(); if (downstreamNode) { mx::NodeDefPtr nodeDef = downstreamNode->getNodeDef(); if (nodeDef) { if (RENDERABLE_TYPES.count(nodeDef->getType())) { foundNode = downstreamNode; break; } } } if (!foundNode) { nextPaths.insert(parent->getNamePath()); } } } if (foundNode) { break; } } // Set up next set of nodes to search downstream testPaths = nextPaths; } // Update rendering. If found use that node, otherwise // use the current fallback of using the first renderable node. if (foundNode) { for (auto uiNode : _graphNodes) { if (uiNode->getNode() == foundNode) { if (_currRenderNode != uiNode) { _currRenderNode = uiNode; _frameCount = ImGui::GetFrameCount(); _renderer->setMaterialCompilation(true); } break; } } } else { _currRenderNode = nullptr; _frameCount = ImGui::GetFrameCount(); _renderer->setMaterialCompilation(true); } } } void Graph::updateMaterials(mx::InputPtr input /* = nullptr */, mx::ValuePtr value /* = nullptr */) { std::string renderablePath; if (_currRenderNode) { if (_currRenderNode->getNode()) { renderablePath = _currRenderNode->getNode()->getNamePath(); } else if (_currRenderNode->getOutput()) { renderablePath = _currRenderNode->getOutput()->getNamePath(); } } if (renderablePath.empty()) { _renderer->updateMaterials(nullptr); } else { if (!input) { mx::ElementPtr elem = nullptr; { elem = _graphDoc->getDescendant(renderablePath); } mx::TypedElementPtr typedElem = elem ? elem->asA() : nullptr; _renderer->updateMaterials(typedElem); } else { std::string name = input->getNamePath(); // Note that if there is a topogical change due to // this value change or a transparency change, then // this is not currently caught here. _renderer->getMaterials()[0]->modifyUniform(name, value); } } } void Graph::setConstant(UiNodePtr node, mx::InputPtr& input, const mx::UIProperties& uiProperties) { ImGui::PushItemWidth(-1); mx::ValuePtr minVal = uiProperties.uiMin; mx::ValuePtr maxVal = uiProperties.uiMax; // If input is a float set the float slider UI to the value if (input->getType() == "float") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { // Update the value to the default for new nodes float prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA() : 0.f; float max = maxVal ? maxVal->asA() : 100.f; float speed = (max - min) / 1000.0f; ImGui::DragFloat("##hidelabel", &temp, speed, min, max); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "integer") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { int prev, temp; prev = temp = val->asA(); int min = minVal ? minVal->asA() : 0; int max = maxVal ? maxVal->asA() : 100; float speed = (max - min) / 100.0f; ImGui::DragInt("##hidelabel", &temp, speed, min, max); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "color3") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { mx::Color3 prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA()[0] : 0.f; float max = maxVal ? maxVal->asA()[0] : 100.f; float speed = (max - min) / 1000.0f; ImGui::PushItemWidth(-100); ImGui::DragFloat3("##hidelabel", &temp[0], speed, min, max); ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::ColorEdit3("##color", &temp[0], ImGuiColorEditFlags_NoInputs); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "color4") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { mx::Color4 prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA()[0] : 0.f; float max = maxVal ? maxVal->asA()[0] : 100.f; float speed = (max - min) / 1000.0f; ImGui::PushItemWidth(-100); ImGui::DragFloat4("##hidelabel", &temp[0], speed, min, max); ImGui::PopItemWidth(); ImGui::SameLine(); // Color edit for the color picker to the right of the color floats ImGui::ColorEdit4("##color", &temp[0], ImGuiColorEditFlags_NoInputs); // Set input value and update materials if different from previous value if (temp != prev) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "vector2") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { mx::Vector2 prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA()[0] : 0.f; float max = maxVal ? maxVal->asA()[0] : 100.f; float speed = (max - min) / 1000.0f; ImGui::DragFloat2("##hidelabel", &temp[0], speed, min, max); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "vector3") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { mx::Vector3 prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA()[0] : 0.f; float max = maxVal ? maxVal->asA()[0] : 100.f; float speed = (max - min) / 1000.0f; ImGui::DragFloat3("##hidelabel", &temp[0], speed, min, max); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "vector4") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { mx::Vector4 prev, temp; prev = temp = val->asA(); float min = minVal ? minVal->asA()[0] : 0.f; float max = maxVal ? maxVal->asA()[0] : 100.f; float speed = (max - min) / 1000.0f; ImGui::DragFloat4("##hidelabel", &temp[0], speed, min, max); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } else if (input->getType() == "string") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { std::string prev, temp; prev = temp = val->asA(); ImGui::InputText("##constant", &temp); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(); } } } else if (input->getType() == "filename") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { std::string prev, temp; prev = temp = val->asA(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.15f, .15f, .15f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.2f, .4f, .6f, 1.0f)); // Browser button to select new file ImGui::PushItemWidth(-100); if (ImGui::Button("Browse")) { _fileDialogImageInputName = input->getName(); _fileDialogImage.setTitle("Node Input Dialog"); _fileDialogImage.open(); _fileDialogImage.setTypeFilters(_imageFilter); } ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::Text("%s", mx::FilePath(temp).getBaseName().c_str()); ImGui::PopStyleColor(); ImGui::PopStyleColor(); // Create and load document from selected file if (_fileDialogImage.hasSelected() && _fileDialogImageInputName == input->getName()) { // Set the new filename to the complete file path mx::FilePath fileName = _fileDialogImage.getSelected(); temp = fileName; // Need to clear the file prefix so that it can find the new file input->setFilePrefix(mx::EMPTY_STRING); _fileDialogImage.clearSelected(); _fileDialogImage.setTypeFilters(std::vector()); _fileDialogImageInputName = ""; } // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValueString(temp); input->setValue(temp, input->getType()); updateMaterials(); } } } else if (input->getType() == "boolean") { mx::ValuePtr val = input->getValue(); if (val && val->isA()) { bool prev, temp; prev = temp = val->asA(); ImGui::Checkbox("", &temp); // Set input value and update materials if different from previous value if (prev != temp) { addNodeInput(_currUiNode, input); input->setValue(temp, input->getType()); updateMaterials(input, input->getValue()); } } } ImGui::PopItemWidth(); } void Graph::setUiNodeInfo(UiNodePtr node, const std::string& type, const std::string& category) { node->setType(type); node->setCategory(category); ++_graphTotalSize; // Create pins if (node->getNodeGraph()) { std::vector outputs = node->getNodeGraph()->getOutputs(); for (mx::OutputPtr out : outputs) { UiPinPtr outPin = std::make_shared(_graphTotalSize, &*out->getName().begin(), out->getType(), node, ax::NodeEditor::PinKind::Output, nullptr, nullptr); ++_graphTotalSize; node->outputPins.push_back(outPin); _currPins.push_back(outPin); } for (mx::InputPtr input : node->getNodeGraph()->getInputs()) { UiPinPtr inPin = std::make_shared(_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr); node->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } } else { if (node->getNode()) { mx::NodeDefPtr nodeDef = node->getNode()->getNodeDef(node->getNode()->getName()); if (nodeDef) { for (mx::InputPtr input : nodeDef->getActiveInputs()) { if (node->getNode()->getInput(input->getName())) { input = node->getNode()->getInput(input->getName()); } UiPinPtr inPin = std::make_shared(_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr); node->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } for (mx::OutputPtr output : nodeDef->getActiveOutputs()) { if (node->getNode()->getOutput(output->getName())) { output = node->getNode()->getOutput(output->getName()); } UiPinPtr outPin = std::make_shared(_graphTotalSize, &*output->getName().begin(), output->getType(), node, ax::NodeEditor::PinKind::Output, nullptr, nullptr); node->outputPins.push_back(outPin); _currPins.push_back(outPin); ++_graphTotalSize; } } } else if (node->getInput()) { UiPinPtr inPin = std::make_shared(_graphTotalSize, &*("Value"), node->getInput()->getType(), node, ax::NodeEditor::PinKind::Input, node->getInput(), nullptr); node->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } else if (node->getOutput()) { UiPinPtr inPin = std::make_shared(_graphTotalSize, &*("input"), node->getOutput()->getType(), node, ax::NodeEditor::PinKind::Input, nullptr, node->getOutput()); node->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } if (node->getInput() || node->getOutput()) { UiPinPtr outPin = std::make_shared(_graphTotalSize, &*("output"), type, node, ax::NodeEditor::PinKind::Output, nullptr, nullptr); ++_graphTotalSize; node->outputPins.push_back(outPin); _currPins.push_back(outPin); } } _graphNodes.push_back(std::move(node)); } void Graph::createNodeUIList(mx::DocumentPtr doc) { _nodesToAdd.clear(); auto nodeDefs = doc->getNodeDefs(); std::unordered_map> groupToNodeDef; std::vector groupList = std::vector(NODE_GROUP_ORDER.begin(), NODE_GROUP_ORDER.end()); for (const auto& nodeDef : nodeDefs) { std::string group = nodeDef->getNodeGroup(); if (group.empty()) { group = NODE_GROUP_ORDER.back(); } // If the group is not in the groupList already (seeded by NODE_GROUP_ORDER) then add it. if (std::find(groupList.begin(), groupList.end(), group) == groupList.end()) { groupList.emplace_back(group); } if (groupToNodeDef.find(group) == groupToNodeDef.end()) { groupToNodeDef[group] = std::vector(); } groupToNodeDef[group].push_back(nodeDef); } for (const auto& group : groupList) { auto it = groupToNodeDef.find(group); if (it != groupToNodeDef.end()) { const auto& groupNodeDefs = it->second; for (const auto& nodeDef : groupNodeDefs) { _nodesToAdd.emplace_back(nodeDef->getName(), nodeDef->getType(), nodeDef->getNodeString(), group); } } } addExtraNodes(); } void Graph::buildUiBaseGraph(mx::DocumentPtr doc) { std::vector nodeGraphs = doc->getNodeGraphs(); std::vector inputNodes = doc->getActiveInputs(); std::vector outputNodes = doc->getOutputs(); std::vector docNodes = doc->getNodes(); mx::ElementPredicate includeElement = getElementPredicate(); _graphNodes.clear(); _currLinks.clear(); _currEdge.clear(); _newLinks.clear(); _currPins.clear(); _graphTotalSize = 1; // Create UiNodes for nodes that belong to the document so they are not in a nodegraph for (mx::NodePtr node : docNodes) { if (!includeElement(node)) continue; std::string name = node->getName(); auto currNode = std::make_shared(name, _graphTotalSize); currNode->setNode(node); setUiNodeInfo(currNode, node->getType(), node->getCategory()); } // Create UiNodes for the nodegraph for (mx::NodeGraphPtr nodeGraph : nodeGraphs) { if (!includeElement(nodeGraph)) continue; std::string name = nodeGraph->getName(); auto currNode = std::make_shared(name, _graphTotalSize); currNode->setNodeGraph(nodeGraph); setUiNodeInfo(currNode, "", "nodegraph"); } for (mx::InputPtr input : inputNodes) { if (!includeElement(input)) continue; auto currNode = std::make_shared(input->getName(), _graphTotalSize); currNode->setInput(input); setUiNodeInfo(currNode, input->getType(), input->getCategory()); } for (mx::OutputPtr output : outputNodes) { if (!includeElement(output)) continue; auto currNode = std::make_shared(output->getName(), _graphTotalSize); currNode->setOutput(output); setUiNodeInfo(currNode, output->getType(), output->getCategory()); } // Create edges for nodegraphs for (mx::NodeGraphPtr graph : nodeGraphs) { for (mx::InputPtr input : graph->getActiveInputs()) { int downNum = -1; int upNum = -1; mx::string nodeGraphName = input->getNodeGraphString(); mx::NodePtr connectedNode = input->getConnectedNode(); if (!nodeGraphName.empty()) { downNum = findNode(graph->getName(), "nodegraph"); upNum = findNode(nodeGraphName, "nodegraph"); } else if (connectedNode) { downNum = findNode(graph->getName(), "nodegraph"); upNum = findNode(connectedNode->getName(), "node"); } if (upNum > -1) { UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNum], input); if (!edgeExists(newEdge)) { _graphNodes[downNum]->edges.push_back(newEdge); _graphNodes[downNum]->setInputNodeNum(1); _graphNodes[upNum]->setOutputConnection(_graphNodes[downNum]); _currEdge.push_back(newEdge); } } } } // Create edges for surface and material nodes for (mx::NodePtr node : docNodes) { mx::NodeDefPtr nD = node->getNodeDef(node->getName()); for (mx::InputPtr input : node->getActiveInputs()) { mx::string nodeGraphName = input->getNodeGraphString(); mx::NodePtr connectedNode = input->getConnectedNode(); mx::OutputPtr connectedOutput = input->getConnectedOutput(); int upNum = -1; int downNum = -1; if (!nodeGraphName.empty()) { upNum = findNode(nodeGraphName, "nodegraph"); downNum = findNode(node->getName(), "node"); } else if (connectedNode) { upNum = findNode(connectedNode->getName(), "node"); downNum = findNode(node->getName(), "node"); } else if (connectedOutput) { upNum = findNode(connectedOutput->getName(), "output"); downNum = findNode(node->getName(), "node"); } else if (!input->getInterfaceName().empty()) { upNum = findNode(input->getInterfaceName(), "input"); downNum = findNode(node->getName(), "node"); } if (upNum != -1) { UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNum], input); if (!edgeExists(newEdge)) { _graphNodes[downNum]->edges.push_back(newEdge); _graphNodes[downNum]->setInputNodeNum(1); _graphNodes[upNum]->setOutputConnection(_graphNodes[downNum]); _currEdge.push_back(newEdge); } } } } } void Graph::buildUiNodeGraph(const mx::NodeGraphPtr& nodeGraphs) { // Clear all values so that ids can start with 0 or 1 _graphNodes.clear(); _currLinks.clear(); _currEdge.clear(); _newLinks.clear(); _currPins.clear(); _graphTotalSize = 1; if (nodeGraphs) { mx::NodeGraphPtr nodeGraph = nodeGraphs; std::vector children = nodeGraph->topologicalSort(); mx::NodeDefPtr nodeDef = nodeGraph->getNodeDef(); mx::NodeDefPtr currNodeDef; // Create input nodes if (nodeDef) { std::vector inputs = nodeDef->getActiveInputs(); for (mx::InputPtr input : inputs) { auto currNode = std::make_shared(input->getName(), _graphTotalSize); currNode->setInput(input); setUiNodeInfo(currNode, input->getType(), input->getCategory()); } } // Search node graph children to create uiNodes for (mx::ElementPtr elem : children) { mx::NodePtr node = elem->asA(); mx::InputPtr input = elem->asA(); mx::OutputPtr output = elem->asA(); std::string name = elem->getName(); auto currNode = std::make_shared(name, _graphTotalSize); if (node) { currNode->setNode(node); setUiNodeInfo(currNode, node->getType(), node->getCategory()); } else if (input) { currNode->setInput(input); setUiNodeInfo(currNode, input->getType(), input->getCategory()); } else if (output) { currNode->setOutput(output); setUiNodeInfo(currNode, output->getType(), output->getCategory()); } } // Write out all connections. std::set processedEdges; for (mx::OutputPtr output : nodeGraph->getOutputs()) { for (mx::Edge edge : output->traverseGraph()) { if (!processedEdges.count(edge)) { mx::ElementPtr upstreamElem = edge.getUpstreamElement(); mx::ElementPtr downstreamElem = edge.getDownstreamElement(); mx::ElementPtr connectingElem = edge.getConnectingElement(); mx::NodePtr upstreamNode = upstreamElem->asA(); mx::NodePtr downstreamNode = downstreamElem->asA(); mx::InputPtr upstreamInput = upstreamElem->asA(); mx::InputPtr downstreamInput = downstreamElem->asA(); mx::OutputPtr upstreamOutput = upstreamElem->asA(); mx::OutputPtr downstreamOutput = downstreamElem->asA(); std::string downName = downstreamElem->getName(); std::string upName = upstreamElem->getName(); std::string upstreamType; std::string downstreamType; if (upstreamNode) { upstreamType = "node"; } else if (upstreamInput) { upstreamType = "input"; } else if (upstreamOutput) { upstreamType = "output"; } if (downstreamNode) { downstreamType = "node"; } else if (downstreamInput) { downstreamType = "input"; } else if (downstreamOutput) { downstreamType = "output"; } int upNode = findNode(upName, upstreamType); int downNode = findNode(downName, downstreamType); if (downNode > 0 && upNode > 0 && _graphNodes[downNode]->getOutput()) { // Create edges for the output nodes UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], nullptr); if (!edgeExists(newEdge)) { _graphNodes[downNode]->edges.push_back(newEdge); _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]); _currEdge.push_back(newEdge); } } else if (connectingElem) { mx::InputPtr connectingInput = connectingElem->asA(); if (connectingInput) { if ((upNode >= 0) && (downNode >= 0)) { UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput); if (!edgeExists(newEdge)) { _graphNodes[downNode]->edges.push_back(newEdge); _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]); _currEdge.push_back(newEdge); } } } } if (upstreamNode) { std::vector ins = upstreamNode->getActiveInputs(); for (mx::InputPtr input : ins) { // Connect input nodes if (input->hasInterfaceName()) { std::string interfaceName = input->getInterfaceName(); int newUp = findNode(interfaceName, "input"); if (newUp >= 0) { mx::InputPtr inputP = std::make_shared(downstreamElem, input->getName()); UiEdge newEdge = UiEdge(_graphNodes[newUp], _graphNodes[upNode], input); if (!edgeExists(newEdge)) { _graphNodes[upNode]->edges.push_back(newEdge); _graphNodes[upNode]->setInputNodeNum(1); _graphNodes[newUp]->setOutputConnection(_graphNodes[upNode]); _currEdge.push_back(newEdge); } } } } } processedEdges.insert(edge); } } } // Second pass to catch all of the connections that arent part of an output for (mx::ElementPtr elem : children) { mx::NodePtr node = elem->asA(); mx::InputPtr inputElem = elem->asA(); mx::OutputPtr output = elem->asA(); if (node) { std::vector inputs = node->getActiveInputs(); for (mx::InputPtr input : inputs) { mx::NodePtr upNode = input->getConnectedNode(); if (upNode) { int upNum = findNode(upNode->getName(), "node"); int downNode = findNode(node->getName(), "node"); if ((upNum >= 0) && (downNode >= 0)) { UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], input); if (!edgeExists(newEdge)) { _graphNodes[downNode]->edges.push_back(newEdge); _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]); _currEdge.push_back(newEdge); } } } else if (input->getInterfaceInput()) { int upNum = findNode(input->getInterfaceInput()->getName(), "input"); int downNode = findNode(node->getName(), "node"); if ((upNum >= 0) && (downNode >= 0)) { UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], input); if (!edgeExists(newEdge)) { _graphNodes[downNode]->edges.push_back(newEdge); _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]); _currEdge.push_back(newEdge); } } } } } else if (output) { mx::NodePtr upNode = output->getConnectedNode(); if (upNode) { int upNum = findNode(upNode->getName(), "node"); int downNode = findNode(output->getName(), "output"); UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], nullptr); if (!edgeExists(newEdge)) { _graphNodes[downNode]->edges.push_back(newEdge); _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]); _currEdge.push_back(newEdge); } } } } } } int Graph::findNode(const std::string& name, const std::string& type) { int count = 0; for (size_t i = 0; i < _graphNodes.size(); i++) { if (_graphNodes[i]->getName() == name) { if (type == "node" && _graphNodes[i]->getNode() != nullptr) { return count; } else if (type == "input" && _graphNodes[i]->getInput() != nullptr) { return count; } else if (type == "output" && _graphNodes[i]->getOutput() != nullptr) { return count; } else if (type == "nodegraph" && _graphNodes[i]->getNodeGraph() != nullptr) { return count; } } count++; } return -1; } void Graph::positionPasteBin(ImVec2 pos) { ImVec2 totalPos = ImVec2(0, 0); ImVec2 avgPos = ImVec2(0, 0); // Get average position of original nodes for (auto pasteNode : _copiedNodes) { ImVec2 origPos = ed::GetNodePosition(pasteNode.first->getId()); totalPos.x += origPos.x; totalPos.y += origPos.y; } avgPos.x = totalPos.x / (int) _copiedNodes.size(); avgPos.y = totalPos.y / (int) _copiedNodes.size(); // Get offset from clicked position ImVec2 offset = ImVec2(0, 0); offset.x = pos.x - avgPos.x; offset.y = pos.y - avgPos.y; for (auto pasteNode : _copiedNodes) { if (!pasteNode.second) { continue; } ImVec2 newPos = ImVec2(0, 0); newPos.x = ed::GetNodePosition(pasteNode.first->getId()).x + offset.x; newPos.y = ed::GetNodePosition(pasteNode.first->getId()).y + offset.y; ed::SetNodePosition(pasteNode.second->getId(), newPos); } } void Graph::createEdge(UiNodePtr upNode, UiNodePtr downNode, mx::InputPtr connectingInput) { if (downNode->getOutput()) { // Create edges for the output nodes UiEdge newEdge = UiEdge(upNode, downNode, nullptr); if (!edgeExists(newEdge)) { downNode->edges.push_back(newEdge); downNode->setInputNodeNum(1); upNode->setOutputConnection(downNode); _currEdge.push_back(newEdge); } } else if (connectingInput) { UiEdge newEdge = UiEdge(upNode, downNode, connectingInput); downNode->edges.push_back(newEdge); downNode->setInputNodeNum(1); upNode->setOutputConnection(downNode); _currEdge.push_back(newEdge); } } void Graph::copyUiNode(UiNodePtr node) { UiNodePtr copyNode = std::make_shared(mx::EMPTY_STRING, int(_graphTotalSize + 1)); ++_graphTotalSize; if (node->getElement()) { std::string newName = _currGraphElem->createValidChildName(node->getName()); if (node->getNode()) { mx::NodePtr mxNode; mxNode = _currGraphElem->addNodeInstance(node->getNode()->getNodeDef()); mxNode->copyContentFrom(node->getNode()); mxNode->setName(newName); copyNode->setNode(mxNode); } else if (node->getInput()) { mx::InputPtr mxInput; mxInput = _currGraphElem->addInput(newName); mxInput->copyContentFrom(node->getInput()); copyNode->setInput(mxInput); } else if (node->getOutput()) { mx::OutputPtr mxOutput; mxOutput = _currGraphElem->addOutput(newName); mxOutput->copyContentFrom(node->getOutput()); mxOutput->setName(newName); copyNode->setOutput(mxOutput); } copyNode->getElement()->setName(newName); copyNode->setName(newName); } else if (node->getNodeGraph()) { _graphDoc->addNodeGraph(); std::string nodeGraphName = _graphDoc->getNodeGraphs().back()->getName(); copyNode->setNodeGraph(_graphDoc->getNodeGraphs().back()); copyNode->setName(nodeGraphName); copyNodeGraph(node, copyNode); } setUiNodeInfo(copyNode, node->getType(), node->getCategory()); _copiedNodes[node] = copyNode; _graphNodes.push_back(copyNode); } void Graph::copyNodeGraph(UiNodePtr origGraph, UiNodePtr copyGraph) { copyGraph->getNodeGraph()->copyContentFrom(origGraph->getNodeGraph()); std::vector inputs = copyGraph->getNodeGraph()->getActiveInputs(); for (mx::InputPtr input : inputs) { std::string newName = _graphDoc->createValidChildName(input->getName()); input->setName(newName); } } void Graph::copyInputs() { for (std::map::iterator iter = _copiedNodes.begin(); iter != _copiedNodes.end(); ++iter) { int count = 0; UiNodePtr origNode = iter->first; UiNodePtr copyNode = iter->second; for (UiPinPtr pin : origNode->inputPins) { if (origNode->getConnectedNode(pin->_name) && !_ctrlClick) { // If original node is connected check if connect node is in copied nodes if (_copiedNodes.find(origNode->getConnectedNode(pin->_name)) != _copiedNodes.end()) { // Set copy node connected to the value at this key createEdge(_copiedNodes[origNode->getConnectedNode(pin->_name)], copyNode, copyNode->inputPins[count]->_input); UiNodePtr upNode = _copiedNodes[origNode->getConnectedNode(pin->_name)]; if (copyNode->getNode() || copyNode->getNodeGraph()) { mx::InputPtr connectingInput = nullptr; copyNode->inputPins[count]->_input->copyContentFrom(pin->_input); // Update value to be empty if (copyNode->getNode() && copyNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) { if (upNode->getOutput()) { copyNode->inputPins[count]->_input->setConnectedOutput(upNode->getOutput()); } else if (upNode->getInput()) { copyNode->inputPins[count]->_input->setConnectedInterfaceName(upNode->getName()); } else { if (upNode->getNodeGraph()) { ed::PinId outputId = getOutputPin(copyNode, upNode, copyNode->inputPins[count]); for (UiPinPtr outPin : upNode->outputPins) { if (outPin->_pinId == outputId) { mx::OutputPtr outputs = upNode->getNodeGraph()->getOutput(outPin->_name); copyNode->inputPins[count]->_input->setConnectedOutput(outputs); } } } else { copyNode->inputPins[count]->_input->setConnectedNode(upNode->getNode()); } } } else { if (upNode->getInput()) { copyNode->inputPins[count]->_input->setConnectedInterfaceName(upNode->getName()); } else { copyNode->inputPins[count]->_input->setConnectedNode(upNode->getNode()); } } copyNode->inputPins[count]->setConnected(true); } else if (copyNode->getOutput() != nullptr) { mx::InputPtr connectingInput = nullptr; copyNode->getOutput()->setConnectedNode(upNode->getNode()); } // Update input node num and output connections copyNode->setInputNodeNum(1); upNode->setOutputConnection(copyNode); } else if (pin->_input) { if (pin->_input->getInterfaceInput()) { copyNode->inputPins[count]->_input->setConnectedInterfaceName(mx::EMPTY_STRING); } copyNode->inputPins[count]->setConnected(false); setDefaults(copyNode->inputPins[count]->_input); copyNode->inputPins[count]->_input->setConnectedNode(nullptr); copyNode->inputPins[count]->_input->setConnectedOutput(nullptr); } } count++; } } } void Graph::addNode(const std::string& category, const std::string& name, const std::string& type) { mx::NodePtr node = nullptr; std::vector matchingNodeDefs; // Create document or node graph is there is not already one if (category == "output") { std::string outName = ""; mx::OutputPtr newOut; // add output as child of correct parent and create valid name outName = _currGraphElem->createValidChildName(name); newOut = _currGraphElem->addOutput(outName, type); auto outputNode = std::make_shared(outName, int(++_graphTotalSize)); outputNode->setOutput(newOut); setUiNodeInfo(outputNode, type, category); return; } if (category == "input") { std::string inName = ""; mx::InputPtr newIn = nullptr; // Add input as child of correct parent and create valid name inName = _currGraphElem->createValidChildName(name); newIn = _currGraphElem->addInput(inName, type); auto inputNode = std::make_shared(inName, int(++_graphTotalSize)); setDefaults(newIn); inputNode->setInput(newIn); setUiNodeInfo(inputNode, type, category); return; } else if (category == "group") { auto groupNode = std::make_shared(name, int(++_graphTotalSize)); // Set message of group UiNode in order to identify it as such groupNode->setMessage("Comment"); setUiNodeInfo(groupNode, type, "group"); // Create ui portions of group node buildGroupNode(_graphNodes.back()); return; } else if (category == "nodegraph") { // Create new mx::NodeGraph and set as current node graph _graphDoc->addNodeGraph(); std::string nodeGraphName = _graphDoc->getNodeGraphs().back()->getName(); auto nodeGraphNode = std::make_shared(nodeGraphName, int(++_graphTotalSize)); // Set mx::Nodegraph as node graph for uiNode nodeGraphNode->setNodeGraph(_graphDoc->getNodeGraphs().back()); setUiNodeInfo(nodeGraphNode, type, "nodegraph"); return; } else { matchingNodeDefs = _graphDoc->getMatchingNodeDefs(category); for (mx::NodeDefPtr nodedef : matchingNodeDefs) { std::string userNodeDefName = getUserNodeDefName(nodedef->getName()); if (userNodeDefName == name) { node = _currGraphElem->addNodeInstance(nodedef, _currGraphElem->createValidChildName(name)); } } } if (node) { int num = 0; int countDef = 0; for (size_t i = 0; i < matchingNodeDefs.size(); i++) { std::string userNodeDefName = getUserNodeDefName(matchingNodeDefs[i]->getName()); if (userNodeDefName == name) { num = countDef; } countDef++; } std::vector defInputs = matchingNodeDefs[num]->getActiveInputs(); // Add inputs to UiNode as pins so that we can later add them to the node if necessary auto newNode = std::make_shared(node->getName(), int(++_graphTotalSize)); newNode->setCategory(category); newNode->setType(type); newNode->setNode(node); newNode->_showAllInputs = true; node->setType(type); ++_graphTotalSize; for (mx::InputPtr input : defInputs) { UiPinPtr inPin = std::make_shared(_graphTotalSize, &*input->getName().begin(), input->getType(), newNode, ax::NodeEditor::PinKind::Input, input, nullptr); newNode->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } std::vector defOutputs = matchingNodeDefs[num]->getActiveOutputs(); for (mx::OutputPtr output : defOutputs) { UiPinPtr outPin = std::make_shared(_graphTotalSize, &*output->getName().begin(), output->getType(), newNode, ax::NodeEditor::PinKind::Output, nullptr, nullptr); newNode->outputPins.push_back(outPin); _currPins.push_back(outPin); ++_graphTotalSize; } _graphNodes.push_back(std::move(newNode)); updateMaterials(); } } int Graph::getNodeId(ed::PinId pinId) { for (UiPinPtr pin : _currPins) { if (pin->_pinId == pinId) { return findNode(pin->_pinNode->getId()); } } return -1; } UiPinPtr Graph::getPin(ed::PinId pinId) { for (UiPinPtr pin : _currPins) { if (pin->_pinId == pinId) { return pin; } } UiPinPtr nullPin = std::make_shared(-10000, "nullPin", "null", nullptr, ax::NodeEditor::PinKind::Output, nullptr, nullptr); return nullPin; } void Graph::drawPinIcon(const std::string& type, bool connected, int alpha) { ax::Drawing::IconType iconType = ax::Drawing::IconType::Flow; ImColor color = ImColor(0, 0, 0, 255); if (_pinColor.find(type) != _pinColor.end()) { color = _pinColor[type]; } color.Value.w = alpha / 255.0f; ax::Widgets::Icon(ImVec2(24, 24), iconType, connected, color, ImColor(32, 32, 32, alpha)); } void Graph::buildGroupNode(UiNodePtr node) { const float commentAlpha = 0.75f; ImGui::PushStyleVar(ImGuiStyleVar_Alpha, commentAlpha); ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(255, 255, 255, 64)); ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(255, 255, 255, 64)); ed::BeginNode(node->getId()); ImGui::PushID(node->getId()); std::string temp = node->getMessage(); ImVec2 messageSize = ImGui::CalcTextSize(temp.c_str()); ImGui::PushItemWidth(messageSize.x + 15); ImGui::InputText("##edit", &temp); node->setMessage(temp); ImGui::PopItemWidth(); ed::Group(ImVec2(300, 200)); ImGui::PopID(); ed::EndNode(); ed::PopStyleColor(2); ImGui::PopStyleVar(); if (ed::BeginGroupHint(node->getId())) { auto bgAlpha = static_cast(ImGui::GetStyle().Alpha * 255); auto min = ed::GetGroupMin(); ImGui::SetCursorScreenPos(min - ImVec2(-8, ImGui::GetTextLineHeightWithSpacing() + 4)); ImGui::BeginGroup(); ImGui::PushID(node->getId() + 1000); std::string tempName = node->getName(); ImVec2 nameSize = ImGui::CalcTextSize(temp.c_str()); ImGui::PushItemWidth(nameSize.x); ImGui::InputText("##edit", &tempName); node->setName(tempName); ImGui::PopID(); ImGui::EndGroup(); auto drawList = ed::GetHintBackgroundDrawList(); ImRect hintBounds = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); ImRect hintFrameBounds = expandImRect(hintBounds, 8, 4); drawList->AddRectFilled( hintFrameBounds.GetTL(), hintFrameBounds.GetBR(), IM_COL32(255, 255, 255, 64 * bgAlpha / 255), 4.0f); drawList->AddRect( hintFrameBounds.GetTL(), hintFrameBounds.GetBR(), IM_COL32(0, 255, 255, 128 * bgAlpha / 255), 4.0f); } ed::EndGroupHint(); } bool Graph::readOnly() { // If the sources are not the same then the current graph cannot be modified return _currGraphElem->getActiveSourceUri() != _graphDoc->getActiveSourceUri(); } void Graph::drawOutputPins(UiNodePtr node, const std::string& longestInputLabel) { std::string longestLabel = longestInputLabel; for (UiPinPtr pin : node->outputPins) { if (pin->_name.size() > longestLabel.size()) longestLabel = pin->_name; } // Create output pins float nodeWidth = ImGui::CalcTextSize(longestLabel.c_str()).x; for (UiPinPtr pin : node->outputPins) { const float indent = nodeWidth - ImGui::CalcTextSize(pin->_name.c_str()).x; ImGui::Indent(indent); ImGui::TextUnformatted(pin->_name.c_str()); ImGui::SameLine(); ed::BeginPin(pin->_pinId, ed::PinKind::Output); bool connected = pin->getConnected(); if (!_pinFilterType.empty()) { drawPinIcon(pin->_type, connected, _pinFilterType == pin->_type ? DEFAULT_ALPHA : FILTER_ALPHA); } else { drawPinIcon(pin->_type, connected, DEFAULT_ALPHA); } ed::EndPin(); ImGui::Unindent(indent); } } void Graph::drawInputPin(UiPinPtr pin) { ed::BeginPin(pin->_pinId, ed::PinKind::Input); ImGui::PushID(int(pin->_pinId.Get())); bool connected = pin->getConnected(); if (!_pinFilterType.empty()) { if (_pinFilterType == pin->_type) { drawPinIcon(pin->_type, connected, DEFAULT_ALPHA); } else { drawPinIcon(pin->_type, connected, FILTER_ALPHA); } } else { drawPinIcon(pin->_type, connected, DEFAULT_ALPHA); } ImGui::PopID(); ed::EndPin(); ImGui::SameLine(); ImGui::TextUnformatted(pin->_name.c_str()); } std::vector Graph::createNodes(bool nodegraph) { std::vector outputNum; for (UiNodePtr node : _graphNodes) { if (node->getCategory() == "group") { buildGroupNode(node); } else { // Color for output pin std::string outputType; if (node->getNode() != nullptr) { ed::BeginNode(node->getId()); ImGui::PushID(node->getId()); ImGui::SetWindowFontScale(1.2f * _fontScale); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(55, 55, 55, 255)), 12.f); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(55, 55, 55, 255)), 0.f); ImGui::Text("%s", node->getName().c_str()); ImGui::SetWindowFontScale(_fontScale); std::string longestInputLabel = node->getName(); for (UiPinPtr pin : node->inputPins) { UiNodePtr upUiNode = node->getConnectedNode(pin->_name); if (upUiNode) { size_t pinIndex = 0; if (upUiNode->outputPins.size() > 0) { const std::string outputString = pin->_input->getOutputString(); if (!outputString.empty()) { for (size_t i = 0; i < upUiNode->outputPins.size(); i++) { UiPinPtr outPin = upUiNode->outputPins[i]; if (outPin->_name == outputString) { pinIndex = i; break; } } } upUiNode->outputPins[pinIndex]->addConnection(pin); pin->addConnection(upUiNode->outputPins[pinIndex]); } pin->setConnected(true); } if (node->_showAllInputs || (pin->getConnected() || node->getNode()->getInput(pin->_name))) { drawInputPin(pin); if (pin->_name.size() > longestInputLabel.size()) longestInputLabel = pin->_name; } } drawOutputPins(node, longestInputLabel); // Set color of output pin if (node->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) { if (node->getOutputConnections().size() > 0) { for (UiNodePtr outputCon : node->getOutputConnections()) { outputNum.push_back(findNode(outputCon->getId())); } } } } else if (node->getInput() != nullptr) { std::string longestInputLabel = node->getName(); ed::BeginNode(node->getId()); ImGui::PushID(node->getId()); ImGui::SetWindowFontScale(1.2f * _fontScale); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0f, -8.0f), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(85, 85, 85, 255)), 12.f); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0f, 3.f), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(85, 85, 85, 255)), 0.f); ImGui::Text("%s", node->getName().c_str()); ImGui::SetWindowFontScale(_fontScale); outputType = node->getInput()->getType(); for (UiPinPtr pin : node->inputPins) { UiNodePtr upUiNode = node->getConnectedNode(node->getName()); if (upUiNode) { if (upUiNode->outputPins.size()) { std::string outString = pin->_output ? pin->_output->getOutputString() : mx::EMPTY_STRING; size_t pinIndex = 0; if (!outString.empty()) { for (size_t i = 0; i < upUiNode->outputPins.size(); i++) { if (upUiNode->outputPins[i]->_name == outString) { pinIndex = i; break; } } } upUiNode->outputPins[pinIndex]->addConnection(pin); pin->addConnection(upUiNode->outputPins[pinIndex]); } pin->setConnected(true); } ed::BeginPin(pin->_pinId, ed::PinKind::Input); if (!_pinFilterType.empty()) { if (_pinFilterType == pin->_type) { drawPinIcon(pin->_type, true, DEFAULT_ALPHA); } else { drawPinIcon(pin->_type, true, FILTER_ALPHA); } } else { drawPinIcon(pin->_type, true, DEFAULT_ALPHA); } ImGui::SameLine(); ImGui::TextUnformatted("value"); ed::EndPin(); if (pin->_name.size() > longestInputLabel.size()) longestInputLabel = pin->_name; } drawOutputPins(node, longestInputLabel); } else if (node->getOutput() != nullptr) { std::string longestInputLabel = node->getName(); ed::BeginNode(node->getId()); ImGui::PushID(node->getId()); ImGui::SetWindowFontScale(1.2f * _fontScale); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(35, 35, 35, 255)), 12.f); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(35, 35, 35, 255)), 0); ImGui::Text("%s", node->getName().c_str()); ImGui::SetWindowFontScale(_fontScale); outputType = node->getOutput()->getType(); for (UiPinPtr pin : node->inputPins) { UiNodePtr upUiNode = node->getConnectedNode(""); if (upUiNode) { if (upUiNode->outputPins.size()) { std::string outString = pin->_output ? pin->_output->getOutputString() : mx::EMPTY_STRING; size_t pinIndex = 0; if (!outString.empty()) { for (size_t i = 0; i < upUiNode->outputPins.size(); i++) { if (upUiNode->outputPins[i]->_name == outString) { pinIndex = i; break; } } } upUiNode->outputPins[pinIndex]->addConnection(pin); pin->addConnection(upUiNode->outputPins[pinIndex]); } } ed::BeginPin(pin->_pinId, ed::PinKind::Input); if (!_pinFilterType.empty()) { if (_pinFilterType == pin->_type) { drawPinIcon(pin->_type, true, DEFAULT_ALPHA); } else { drawPinIcon(pin->_type, true, FILTER_ALPHA); } } else { drawPinIcon(pin->_type, true, DEFAULT_ALPHA); } ImGui::SameLine(); ImGui::TextUnformatted("input"); ed::EndPin(); if (pin->_name.size() > longestInputLabel.size()) longestInputLabel = pin->_name; } drawOutputPins(node, longestInputLabel); if (nodegraph) { outputNum.push_back(findNode(node->getId())); } } else if (node->getNodeGraph() != nullptr) { std::string longestInputLabel = node->getName(); ed::BeginNode(node->getId()); ImGui::PushID(node->getId()); ImGui::SetWindowFontScale(1.2f * _fontScale); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(35, 35, 35, 255)), 12.f); ImGui::GetWindowDrawList()->AddRectFilled( ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3), ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f), ImColor(ImColor(35, 35, 35, 255)), 0); ImGui::Text("%s", node->getName().c_str()); ImGui::SetWindowFontScale(_fontScale); for (UiPinPtr pin : node->inputPins) { if (node->getConnectedNode(pin->_name) != nullptr) { pin->setConnected(true); } if (node->_showAllInputs || (pin->getConnected() || node->getNodeGraph()->getInput(pin->_name))) { drawInputPin(pin); if (pin->_name.size() > longestInputLabel.size()) longestInputLabel = pin->_name; } } drawOutputPins(node, longestInputLabel); } ImGui::PopID(); ed::EndNode(); } } ImGui::SetWindowFontScale(_fontScale); return outputNum; } void Graph::addNodeInput(UiNodePtr node, mx::InputPtr& input) { if (node->getNode()) { if (!node->getNode()->getInput(input->getName())) { input = node->getNode()->addInput(input->getName(), input->getType()); input->setConnectedNode(nullptr); } } } void Graph::setDefaults(mx::InputPtr input) { if (input->getType() == "float") { input->setValue(0.f, "float"); } else if (input->getType() == "integer") { input->setValue(0, "integer"); } else if (input->getType() == "color3") { input->setValue(mx::Color3(0.f, 0.f, 0.f), "color3"); } else if (input->getType() == "color4") { input->setValue(mx::Color4(0.f, 0.f, 0.f, 1.f), "color4"); } else if (input->getType() == "vector2") { input->setValue(mx::Vector2(0.f, 0.f), "vector2"); } else if (input->getType() == "vector3") { input->setValue(mx::Vector3(0.f, 0.f, 0.f), "vector3"); } else if (input->getType() == "vector4") { input->setValue(mx::Vector4(0.f, 0.f, 0.f, 0.f), "vector4"); } else if (input->getType() == "string") { input->setValue("", "string"); } else if (input->getType() == "filename") { input->setValue("", "filename"); } else if (input->getType() == "boolean") { input->setValue(false, "boolean"); } } void Graph::addLink(ed::PinId startPinId, ed::PinId endPinId) { // 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) { ed::RejectNewItem(); return; } // Perform type check bool typesMatch = (outputPin->_type == inputPin->_type); if (!typesMatch) { ed::RejectNewItem(); showLabel("Invalid connection due to mismatched types", ImColor(50, 50, 50, 255)); return; } // Perform kind check bool kindsMatch = (outputPin->_kind == inputPin->_kind); if (kindsMatch) { ed::RejectNewItem(); showLabel("Invalid connection due to same input/output kind", ImColor(50, 50, 50, 255)); return; } int upNode = getNodeId(outputPinId); int downNode = getNodeId(inputPinId); UiNodePtr uiDownNode = _graphNodes[downNode]; UiNodePtr uiUpNode = _graphNodes[upNode]; if (!uiDownNode || !uiUpNode) { ed::RejectNewItem(); return; } // Make sure there is an implementation for node const mx::ShaderGenerator& shadergen = _renderer->getGenContext().getShaderGenerator(); // 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("Invalid Connection: Node does not have an implementation", ImColor(50, 50, 50, 255)); return; } } if (ed::AcceptNewItem()) { // If the accepting node already has a link, remove it if (inputPin->_connected) { for (auto iter = _currLinks.begin(); iter != _currLinks.end(); ++iter) { if (iter->_endAttr == end_attr) { // Found existing link - remove it; adapted from deleteLink // note: ed::BreakLinks doesn't work as the order ends up inaccurate deleteLinkInfo(iter->_startAttr, iter->_endAttr); _currLinks.erase(iter); break; } } } // 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); inputPin->addConnection(outputPin); outputPin->addConnection(inputPin); outputPin->setConnected(true); inputPin->setConnected(true); if (uiDownNode->getNode() || uiDownNode->getNodeGraph()) { mx::InputPtr connectingInput = nullptr; for (UiPinPtr pin : uiDownNode->inputPins) { if (pin->_pinId == inputPinId) { addNodeInput(uiDownNode, pin->_input); // Update value to be empty if (uiDownNode->getNode() && uiDownNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) { if (uiUpNode->getOutput() != nullptr) { pin->_input->setConnectedOutput(uiUpNode->getOutput()); } else if (uiUpNode->getInput() != nullptr) { pin->_input->setConnectedInterfaceName(uiUpNode->getName()); } else { if (uiUpNode->getNodeGraph() != nullptr) { for (UiPinPtr outPin : uiUpNode->outputPins) { // Set pin connection to correct output if (outPin->_pinId == outputPinId) { mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name); pin->_input->setConnectedOutput(outputs); } } } else { pin->_input->setConnectedNode(uiUpNode->getNode()); } } } else { if (uiUpNode->getInput()) { pin->_input->setConnectedInterfaceName(uiUpNode->getName()); } else { if (uiUpNode->getNode()) { mx::NodePtr upstreamNode = _graphNodes[upNode]->getNode(); mx::NodeDefPtr upstreamNodeDef = upstreamNode->getNodeDef(); bool isMultiOutput = upstreamNodeDef ? upstreamNodeDef->getOutputs().size() > 1 : false; if (!isMultiOutput) { pin->_input->setConnectedNode(uiUpNode->getNode()); } else { for (UiPinPtr outPin : _graphNodes[upNode]->outputPins) { // Set pin connection to correct output if (outPin->_pinId == outputPinId) { mx::OutputPtr outputs = uiUpNode->getNode()->getOutput(outPin->_name); if (!outputs) { outputs = uiUpNode->getNode()->addOutput(outPin->_name, pin->_input->getType()); } pin->_input->setConnectedOutput(outputs); } } } } else if (uiUpNode->getNodeGraph()) { for (UiPinPtr outPin : uiUpNode->outputPins) { // Set pin connection to correct output if (outPin->_pinId == outputPinId) { mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name); pin->_input->setConnectedOutput(outputs); } } } } } pin->setConnected(true); 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 { // 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); // Update input node num and output connections _graphNodes[downNode]->setInputNodeNum(1); _graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]); } } } } void Graph::removeEdge(int downNode, int upNode, UiPinPtr pin) { int num = _graphNodes[downNode]->getEdgeIndex(_graphNodes[upNode]->getId(), pin); if (num != -1) { if (_graphNodes[downNode]->edges.size() == 1) { _graphNodes[downNode]->edges.erase(_graphNodes[downNode]->edges.begin() + 0); } else if (_graphNodes[downNode]->edges.size() > 1) { _graphNodes[downNode]->edges.erase(_graphNodes[downNode]->edges.begin() + num); } } _graphNodes[downNode]->setInputNodeNum(-1); _graphNodes[upNode]->removeOutputConnection(_graphNodes[downNode]->getName()); } void Graph::deleteLinkInfo(int startAttr, int endAttr) { int upNode = getNodeId(startAttr); int downNode = getNodeId(endAttr); if (upNode == -1 || downNode == -1) { return; } // Change input to default value if (_graphNodes[downNode]->getNode()) { mx::NodeDefPtr nodeDef = _graphNodes[downNode]->getNode()->getNodeDef(_graphNodes[downNode]->getNode()->getName()); for (UiPinPtr pin : _graphNodes[downNode]->inputPins) { if ((int) pin->_pinId.Get() == endAttr) { removeEdge(downNode, upNode, pin); mx::ValuePtr val = nodeDef->getActiveInput(pin->_input->getName())->getValue(); if (_graphNodes[downNode]->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING && _graphNodes[upNode]->getNodeGraph()) { pin->_input->setConnectedOutput(nullptr); } else { pin->_input->setConnectedNode(nullptr); } if (_graphNodes[upNode]->getInput()) { // Remove interface value in order to set the default of the input pin->_input->setConnectedInterfaceName(mx::EMPTY_STRING); setDefaults(pin->_input); setDefaults(_graphNodes[upNode]->getInput()); } for (UiPinPtr connect : pin->_connections) { pin->deleteConnection(connect); } // Remove any output reference pin->_input->removeAttribute(mx::PortElement::OUTPUT_ATTRIBUTE); pin->setConnected(false); // If a value exists update the input with it if (val) { pin->_input->setValueString(val->getValueString()); } } } } else if (_graphNodes[downNode]->getNodeGraph()) { // Set default values for nodegraph node pins ie nodegraph inputs mx::NodeDefPtr nodeDef = _graphNodes[downNode]->getNodeGraph()->getNodeDef(); for (UiPinPtr pin : _graphNodes[downNode]->inputPins) { if ((int) pin->_pinId.Get() == endAttr) { removeEdge(downNode, upNode, pin); if (_graphNodes[upNode]->getInput()) { _graphNodes[downNode]->getNodeGraph()->getInput(pin->_name)->setConnectedInterfaceName(mx::EMPTY_STRING); setDefaults(_graphNodes[upNode]->getInput()); } for (UiPinPtr connect : pin->_connections) { pin->deleteConnection(connect); } pin->_input->setConnectedNode(nullptr); pin->setConnected(false); setDefaults(pin->_input); } } } else if (_graphNodes[downNode]->getOutput()) { for (UiPinPtr pin : _graphNodes[downNode]->inputPins) { if ((int) pin->_pinId.Get() == endAttr) { removeEdge(downNode, upNode, pin); _graphNodes[downNode]->getOutput()->removeAttribute("nodename"); for (UiPinPtr connect : pin->_connections) { pin->deleteConnection(connect); } pin->setConnected(false); } } } } void Graph::deleteLink(ed::LinkId deletedLinkId) { // If you agree that link can be deleted, accept deletion. if (ed::AcceptDeletedItem()) { _renderer->setMaterialCompilation(true); _frameCount = ImGui::GetFrameCount(); int link_id = int(deletedLinkId.Get()); // Then remove link from your data. int pos = findLinkPosition(link_id); // Link start -1 equals node num Link currLink = _currLinks[pos]; deleteLinkInfo(currLink._startAttr, currLink._endAttr); _currLinks.erase(_currLinks.begin() + pos); } } void Graph::deleteNode(UiNodePtr node) { // Delete link for (UiPinPtr inputPin : node->inputPins) { UiNodePtr upNode = node->getConnectedNode(inputPin->_name); if (upNode) { upNode->removeOutputConnection(node->getName()); int num = node->getEdgeIndex(upNode->getId(), inputPin); // Erase edge between node and up node if (num != -1) { if (node->edges.size() == 1) { node->edges.erase(node->edges.begin() + 0); } else if (node->edges.size() > 1) { node->edges.erase(node->edges.begin() + num); } } } } for (UiPinPtr outputPin : node->outputPins) { // Update downNode info for (UiPinPtr pin : outputPin.get()->getConnections()) { mx::ValuePtr val; if (pin->_pinNode->getNode()) { mx::NodeDefPtr nodeDef = pin->_pinNode->getNode()->getNodeDef(pin->_pinNode->getNode()->getName()); val = nodeDef->getActiveInput(pin->_input->getName())->getValue(); if (pin->_pinNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) { pin->_input->setConnectedOutput(nullptr); } else { pin->_input->setConnectedNode(nullptr); } if (node->getInput()) { // Remove interface in order to set the default of the input pin->_input->setConnectedInterfaceName(mx::EMPTY_STRING); setDefaults(pin->_input); setDefaults(node->getInput()); } } else if (pin->_pinNode->getNodeGraph()) { if (node->getInput()) { pin->_pinNode->getNodeGraph()->getInput(pin->_name)->setConnectedInterfaceName(mx::EMPTY_STRING); setDefaults(node->getInput()); } pin->_input->setConnectedNode(nullptr); pin->setConnected(false); setDefaults(pin->_input); } pin->setConnected(false); if (val) { pin->_input->setValueString(val->getValueString()); } int num = pin->_pinNode->getEdgeIndex(node->getId(), pin); if (num != -1) { if (pin->_pinNode->edges.size() == 1) { pin->_pinNode->edges.erase(pin->_pinNode->edges.begin() + 0); } else if (pin->_pinNode->edges.size() > 1) { pin->_pinNode->edges.erase(pin->_pinNode->edges.begin() + num); } } pin->_pinNode->setInputNodeNum(-1); // Not really necessary since it will be deleted node->removeOutputConnection(pin->_pinNode->getName()); } } // Remove from NodeGraph // All link information is handled in delete link which is called before this int nodeNum = findNode(node->getId()); _currGraphElem->removeChild(node->getName()); _graphNodes.erase(_graphNodes.begin() + nodeNum); } void Graph::addNodeGraphPins() { for (UiNodePtr node : _graphNodes) { if (node->getNodeGraph()) { if (node->inputPins.size() != node->getNodeGraph()->getInputs().size()) { for (mx::InputPtr input : node->getNodeGraph()->getInputs()) { std::string name = input->getName(); auto result = std::find_if(node->inputPins.begin(), node->inputPins.end(), [name](UiPinPtr x) { return x->_name == name; }); if (result == node->inputPins.end()) { UiPinPtr inPin = std::make_shared(++_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr); node->inputPins.push_back(inPin); _currPins.push_back(inPin); ++_graphTotalSize; } } } if (node->outputPins.size() != node->getNodeGraph()->getOutputs().size()) { for (mx::OutputPtr output : node->getNodeGraph()->getOutputs()) { std::string name = output->getName(); auto result = std::find_if(node->outputPins.begin(), node->outputPins.end(), [name](UiPinPtr x) { return x->_name == name; }); if (result == node->outputPins.end()) { UiPinPtr outPin = std::make_shared(++_graphTotalSize, &*output->getName().begin(), output->getType(), node, ax::NodeEditor::PinKind::Output, nullptr, nullptr); ++_graphTotalSize; node->outputPins.push_back(outPin); _currPins.push_back(outPin); } } } } } } void Graph::upNodeGraph() { if (!_graphStack.empty()) { savePosition(); _graphNodes = _graphStack.top(); _currPins = _pinStack.top(); _graphTotalSize = _sizeStack.top(); addNodeGraphPins(); _graphStack.pop(); _pinStack.pop(); _sizeStack.pop(); _currGraphName.pop_back(); _initial = true; ed::NavigateToContent(); if (_currUiNode) { ed::DeselectNode(_currUiNode->getId()); _currUiNode = nullptr; } _prevUiNode = nullptr; _isNodeGraph = false; _currGraphElem = _graphDoc; _initial = true; } } void Graph::clearGraph() { _graphNodes.clear(); _currLinks.clear(); _currEdge.clear(); _newLinks.clear(); _currPins.clear(); _graphDoc = mx::createDocument(); _graphDoc->setDataLibrary(_stdLib); _currGraphElem = _graphDoc; if (_currUiNode != nullptr) { ed::DeselectNode(_currUiNode->getId()); _currUiNode = nullptr; } _prevUiNode = nullptr; _currRenderNode = nullptr; _isNodeGraph = false; _currGraphName.clear(); _renderer->setDocument(_graphDoc); _renderer->updateMaterials(nullptr); } void Graph::loadGraphFromFile(bool prompt) { // Deselect node before loading new file if (_currUiNode) { ed::DeselectNode(_currUiNode->getId()); _currUiNode = nullptr; } if (prompt || _materialFilename.isEmpty()) { _fileDialog.setTitle("Open File"); _fileDialog.setTypeFilters(_mtlxFilter); _fileDialog.open(); } else { _graphDoc = loadDocument(_materialFilename); // Rebuild the UI _initial = true; buildUiBaseGraph(_graphDoc); _currGraphElem = _graphDoc; _prevUiNode = nullptr; _renderer->setDocument(_graphDoc); _renderer->updateMaterials(nullptr); } } void Graph::saveGraphToFile() { _fileDialogSave.setTypeFilters(_mtlxFilter); _fileDialogSave.setTitle("Save File As"); _fileDialogSave.open(); } void Graph::loadGeometry() { _fileDialogGeom.setTitle("Load Geometry"); _fileDialogGeom.setTypeFilters(_geomFilter); _fileDialogGeom.open(); } void Graph::graphButtons() { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.15f, .15f, .15f, 1.0f)); ImGui::SetWindowFontScale(_fontScale); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { // Buttons for loading and saving a .mtlx if (ImGui::MenuItem("New", "Ctrl-N")) { clearGraph(); } else if (ImGui::MenuItem("Open", "Ctrl-O")) { loadGraphFromFile(true); } else if (ImGui::MenuItem("Reload", "Ctrl-R")) { loadGraphFromFile(false); } else if (ImGui::MenuItem("Save", "Ctrl-S")) { saveGraphToFile(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Graph")) { if (ImGui::MenuItem("Auto Layout")) { _autoLayout = true; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Viewer")) { if (ImGui::MenuItem("Load Geometry")) { loadGeometry(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Options")) { ImGui::Checkbox("Save Node Positions", &_saveNodePositions); ImGui::EndMenu(); } if (ImGui::Button("Help")) { ImGui::OpenPopup("Help"); } if (ImGui::BeginPopup("Help")) { showHelp(); ImGui::EndPopup(); } ImGui::EndMenuBar(); } // Menu keys ImGuiIO& guiIO = ImGui::GetIO(); if (guiIO.KeyCtrl && !_fileDialogSave.isOpened() && !_fileDialog.isOpened() && !_fileDialogGeom.isOpened()) { if (ImGui::IsKeyReleased(ImGuiKey_O)) { loadGraphFromFile(true); } else if (ImGui::IsKeyReleased(ImGuiKey_N)) { clearGraph(); } else if (ImGui::IsKeyReleased(ImGuiKey_R)) { loadGraphFromFile(false); } else if (ImGui::IsKeyReleased(ImGuiKey_S)) { saveGraphToFile(); } } // Split window into panes for NodeEditor static float leftPaneWidth = 375.0f; static float rightPaneWidth = 750.0f; splitter(true, 4.0f, &leftPaneWidth, &rightPaneWidth, 20.0f, 20.0f); // Create back button and graph hierarchy name display ImGui::Indent(leftPaneWidth + 15.f); if (ImGui::Button("<")) { upNodeGraph(); } ImGui::SameLine(); if (!_currGraphName.empty()) { for (std::string name : _currGraphName) { ImGui::Text("%s", name.c_str()); ImGui::SameLine(); if (name != _currGraphName.back()) { ImGui::Text(">"); ImGui::SameLine(); } } } ImVec2 windowPos2 = ImGui::GetWindowPos(); ImGui::Unindent(leftPaneWidth + 15.f); ImGui::PopStyleColor(); ImGui::NewLine(); // Create two windows using splitter float paneWidth = (leftPaneWidth - 2.0f); float aspectRatio = _renderer->getPixelRatio(); ImVec2 screenSize = ImVec2(paneWidth, paneWidth / aspectRatio); ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 tempWindowPos = ImGui::GetCursorPos(); bool cursorInRenderView = mousePos.x > tempWindowPos.x && mousePos.x < (tempWindowPos.x + screenSize.x) && mousePos.y > tempWindowPos.y && mousePos.y < (tempWindowPos.y + screenSize.y); ImGuiWindowFlags windowFlags = 0; if (cursorInRenderView) { windowFlags |= ImGuiWindowFlags_NoScrollWithMouse; } ImGui::BeginChild("Selection", ImVec2(paneWidth, 0), false, windowFlags); ImVec2 windowPos = ImGui::GetWindowPos(); // Update cursorInRenderView to account for other windows overlapping the Render View (e.g. Menu dropdown). cursorInRenderView &= ImGui::IsWindowHovered(ImGuiHoveredFlags_None); // Update cursorInRenderView to account for visible scrollbar and scroll amount. ImGuiContext* context = ImGui::GetCurrentContext(); bool hasScrollbar = context->CurrentWindow->ScrollbarY; cursorInRenderView &= hasScrollbar ? mousePos.x < (tempWindowPos.x + screenSize.x - ImGui::GetStyle().ScrollbarSize) : true; cursorInRenderView &= hasScrollbar ? mousePos.y < (tempWindowPos.y + screenSize.y - ImGui::GetScrollY()) : true; // RenderView window ImVec2 wsize = ImVec2((float) _renderer->getViewWidth(), (float) _renderer->getViewHeight()); _renderer->setViewWidth((int) screenSize[0]); _renderer->setViewHeight((int) screenSize[1]); if (_renderer) { glEnable(GL_FRAMEBUFFER_SRGB); _renderer->getViewCamera()->setViewportSize(mx::Vector2(screenSize[0], screenSize[1])); GLuint64 my_image_texture = _renderer->_textureID; mx::Vector2 vec = _renderer->getViewCamera()->getViewportSize(); // current image has correct color space but causes problems for gui ImGui::Image((ImTextureID) my_image_texture, screenSize, ImVec2(0, 1), ImVec2(1, 0)); } ImGui::Separator(); // Property editor for current nodes propertyEditor(); ImGui::EndChild(); ImGui::SameLine(0.0f, 12.0f); if (cursorInRenderView) { handleRenderViewInputs(); } } void Graph::propertyEditor() { // Get parent dimensions ImVec2 textPos = ImGui::GetCursorScreenPos(); // Position for the background float parentWidth = ImGui::GetContentRegionAvail().x; // Available width in the parent // Draw the title bar const ImGuiStyle& style = ImGui::GetStyle(); ImVec4 menuBarBgColor = style.Colors[ImGuiCol_MenuBarBg]; ImU32 bgColor = ImGui::ColorConvertFloat4ToU32(menuBarBgColor); // Convert to 32-bit color ImDrawList* drawList = ImGui::GetWindowDrawList(); drawList->AddRectFilled(textPos, ImVec2(textPos.x + parentWidth, textPos.y + ImGui::GetTextLineHeight()), bgColor); ImGui::Text("Node Property Editor"); if (_currUiNode) { // Set and edit name ImGui::Text("Name: "); ImGui::SameLine(); std::string original = _currUiNode->getName(); std::string temp = original; float availableWidth = ImGui::GetContentRegionAvail().x; ImGui::PushItemWidth(availableWidth); ImGui::InputText("##edit", &temp); ImGui::PopItemWidth(); std::string docString = "NodeDef Doc String: \n"; if (_currUiNode->getNode()) { if (temp != original) { std::string name = _currUiNode->getNode()->getParent()->createValidChildName(temp); std::vector downstreamNodes = _currUiNode->getOutputConnections(); for (UiNodePtr uiNode : downstreamNodes) { if (!uiNode->getInput() && uiNode->getNode()) { for (mx::InputPtr input : uiNode->getNode()->getActiveInputs()) { if (input->getConnectedNode() == _currUiNode->getNode()) { _currUiNode->getNode()->setName(name); uiNode->getNode()->setConnectedNode(input->getName(), _currUiNode->getNode()); } } } } _currUiNode->setName(name); _currUiNode->getNode()->setName(name); } } else if (_currUiNode->getInput()) { if (temp != original) { std::string name = _currUiNode->getInput()->getParent()->createValidChildName(temp); std::vector downstreamNodes = _currUiNode->getOutputConnections(); for (UiNodePtr uiNode : downstreamNodes) { if (uiNode->getInput() == nullptr) { if (uiNode->getNode()) { for (mx::InputPtr input : uiNode->getNode()->getActiveInputs()) { if (input->getInterfaceInput() == _currUiNode->getInput()) { _currUiNode->getInput()->setName(name); mx::ValuePtr val = _currUiNode->getInput()->getValue(); input->setConnectedInterfaceName(name); mx::InputPtr pt = input->getInterfaceInput(); } } } else { uiNode->getOutput()->setConnectedNode(_currUiNode->getNode()); } } } _currUiNode->getInput()->setName(name); _currUiNode->setName(name); } } else if (_currUiNode->getOutput()) { if (temp != original) { std::string name = _currUiNode->getOutput()->getParent()->createValidChildName(temp); _currUiNode->getOutput()->setName(name); _currUiNode->setName(name); } } else if (_currUiNode->getCategory() == "group") { _currUiNode->setName(temp); } else if (_currUiNode->getCategory() == "nodegraph") { if (temp != original) { std::string name = _currUiNode->getNodeGraph()->getParent()->createValidChildName(temp); _currUiNode->getNodeGraph()->setName(name); _currUiNode->setName(name); for (UiNodePtr node : _graphNodes) { if (!node->getInput()) { std::vector inputs = node->inputPins; for (size_t i = 0; i < inputs.size(); i++) { const std::string& inputName = inputs[i]->_name; UiNodePtr inputNode = node->getConnectedNode(inputName); if (inputNode && inputNode->getName() == name && node->getNode()) { node->getNode()->getInput(inputName)->setAttribute("nodegraph", name); } } } } } } const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing() * 1.3f; const int SCROLL_LINE_COUNT = 20; ImGuiTableFlags tableFlags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_NoBordersInBody; ImGui::Text("Category:"); ImGui::SameLine(); // Change button color to match background ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.096f, .096f, .096f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.1f, .1f, .1f, 1.0f)); if (_currUiNode->getNode()) { ImGui::NextColumn(); ImGui::Text("%s", _currUiNode->getNode()->getCategory().c_str()); docString += _currUiNode->getNode()->getCategory(); if (_currUiNode->getNode()->getNodeDef()) { docString += ":"; docString += _currUiNode->getNode()->getNodeDef()->getDocString() + "\n"; } if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("%s", _currUiNode->getNode()->getNodeDef()->getDocString().c_str()); } ImGui::Checkbox("Show all inputs", &_currUiNode->_showAllInputs); int count = 0; for (UiPinPtr input : _currUiNode->inputPins) { if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNode()->getInput(input->_name))) { count++; } } if (count) { ImVec2 tableSize(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count)); bool haveTable = ImGui::BeginTable("inputs_node_table", 2, tableFlags, tableSize); if (haveTable) { ImGui::SetWindowFontScale(_fontScale); for (UiPinPtr input : _currUiNode->inputPins) { if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNode()->getInput(input->_name))) { ImGui::TableNextRow(); ImGui::TableNextColumn(); mx::UIProperties uiProperties; mx::getUIProperties(input->_input, mx::EMPTY_STRING, uiProperties); std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : input->_input->getName(); mx::OutputPtr out = input->_input->getConnectedOutput(); // Set comment help box ImGui::PushID(int(input->_pinId.Get())); ImGui::Text("%s", inputLabel.c_str()); mx::InputPtr tempInt = _currUiNode->getNode()->getNodeDef()->getActiveInput(input->_input->getName()); docString += input->_name; docString += ": "; if (tempInt) { std::string newStr = _currUiNode->getNode()->getNodeDef()->getActiveInput(input->_input->getName())->getDocString(); if (newStr != mx::EMPTY_STRING) { docString += newStr; } } docString += "\t \n"; // Set constant sliders for input values ImGui::TableNextColumn(); if (!input->getConnected()) { setConstant(_currUiNode, input->_input, uiProperties); } else { std::string typeText = " [" + input->_input->getType() + "]"; ImGui::Text("%s", typeText.c_str()); } ImGui::PopID(); } } ImGui::EndTable(); ImGui::SetWindowFontScale(1.0f); } } } else if (_currUiNode->getInput() != nullptr) { ImGui::Text("%s", _currUiNode->getCategory().c_str()); std::vector inputs = _currUiNode->inputPins; int count = static_cast(inputs.size()); if (count) { bool haveTable = ImGui::BeginTable("inputs_input_table", 2, tableFlags, ImVec2(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count))); if (haveTable) { ImGui::SetWindowFontScale(_fontScale); for (size_t i = 0; i < inputs.size(); i++) { ImGui::TableNextRow(); ImGui::TableNextColumn(); mx::InputPtr mxinput = inputs[i]->_input; mx::UIProperties uiProperties; mx::getUIProperties(mxinput, mx::EMPTY_STRING, uiProperties); std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : mxinput->getName(); // Set comment help box ImGui::PushID(int(inputs[i]->_pinId.Get())); ImGui::Text("%s", inputLabel.c_str()); ImGui::TableNextColumn(); // Set constant sliders for input values if (!inputs[i]->getConnected()) { setConstant(_currUiNode, inputs[i]->_input, uiProperties); } else { std::string typeText = " [" + inputs[i]->_input->getType() + "]"; ImGui::Text("%s", typeText.c_str()); } ImGui::PopID(); } ImGui::EndTable(); ImGui::SetWindowFontScale(1.0f); } } } else if (_currUiNode->getOutput() != nullptr) { ImGui::Text("%s", _currUiNode->getOutput()->getCategory().c_str()); } else if (_currUiNode->getNodeGraph() != nullptr) { std::vector inputs = _currUiNode->inputPins; ImGui::Text("%s", _currUiNode->getCategory().c_str()); int count = 0; for (UiPinPtr input : inputs) { if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNodeGraph()->getInput(input->_name))) { count++; } } if (count) { bool haveTable = ImGui::BeginTable("inputs_nodegraph_table", 2, tableFlags, ImVec2(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count))); if (haveTable) { ImGui::SetWindowFontScale(_fontScale); for (UiPinPtr input : inputs) { if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNodeGraph()->getInput(input->_name))) { ImGui::TableNextRow(); ImGui::TableNextColumn(); mx::InputPtr mxinput = input->_input; mx::UIProperties uiProperties; mx::getUIProperties(mxinput, mx::EMPTY_STRING, uiProperties); std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : mxinput->getName(); // Set comment help box ImGui::PushID(int(input->_pinId.Get())); ImGui::Text("%s", inputLabel.c_str()); docString += _currUiNode->getNodeGraph()->getActiveInput(input->_input->getName())->getDocString(); ImGui::TableNextColumn(); if (!input->_input->getConnectedNode() && _currUiNode->getNodeGraph()->getActiveInput(input->_input->getName())) { setConstant(_currUiNode, input->_input, uiProperties); } else { std::string typeText = " [" + input->_input->getType() + "]"; ImGui::Text("%s", typeText.c_str()); } ImGui::PopID(); } } ImGui::EndTable(); ImGui::SetWindowFontScale(1.0f); } } ImGui::Checkbox("Show all inputs", &_currUiNode->_showAllInputs); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); if (ImGui::Button("Node Info")) { ImGui::OpenPopup("docstring"); } if (ImGui::BeginPopup("docstring")) { ImGui::SetWindowFontScale(_fontScale); ImGui::Text("%s", docString.c_str()); ImGui::SetWindowFontScale(1.0f); ImGui::EndPopup(); } } } void Graph::showHelp() const { ImGui::Text("MATERIALX GRAPH EDITOR HELP"); if (ImGui::CollapsingHeader("Graph")) { if (ImGui::TreeNode("Navigation")) { ImGui::BulletText("F : Frame selected nodes in graph."); ImGui::BulletText("RIGHT MOUSE button to pan."); ImGui::BulletText("SCROLL WHEEL to zoom."); ImGui::BulletText("\"<\" BUTTON to view parent of current graph"); ImGui::TreePop(); } if (ImGui::TreeNode("Editing")) { ImGui::BulletText("TAB : Show popup menu to add new nodes."); ImGui::BulletText("CTRL-C : Copy selected nodes to clipboard."); ImGui::BulletText("CTRL-V : Paste clipboard to graph."); ImGui::BulletText("CTRL-F : Find a node by name."); ImGui::BulletText("CTRL-X : Delete selected nodes and add to clipboard."); ImGui::BulletText("DELETE : Delete selected nodes or connections."); ImGui::TreePop(); } } if (ImGui::CollapsingHeader("Viewer")) { ImGui::BulletText("LEFT MOUSE button to tumble."); ImGui::BulletText("RIGHT MOUSE button to pan."); ImGui::BulletText("SCROLL WHEEL to zoom."); ImGui::BulletText("Keypad +/- to zoom in fixed increments"); } if (ImGui::CollapsingHeader("Property Editor")) { ImGui::BulletText("UP/DOWN ARROW to move between inputs."); ImGui::BulletText("LEFT-MOUSE DRAG to modify values while entry field is in focus."); ImGui::BulletText("DBL_CLICK or CTRL+CLICK LEFT-MOUSE on entry field to input values."); ImGui::Separator(); ImGui::BulletText("\"Show all inputs\" Will toggle between showing all inputs and\n only those that have been modified."); ImGui::BulletText("\"Node Info\" Will toggle showing node information."); } } void Graph::addNodePopup(bool cursor) { bool open_AddPopup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow) && ImGui::IsKeyReleased(ImGuiKey_Tab); static char input[32]{ "" }; if (open_AddPopup) { cursor = true; ImGui::OpenPopup("add node"); } if (ImGui::BeginPopup("add node")) { ImGui::Text("Add Node"); ImGui::Separator(); if (cursor) { ImGui::SetKeyboardFocusHere(); } ImGui::InputText("##input", input, sizeof(input)); std::string subs(input); // Input string length // Filter extra nodes - includes inputs, outputs, groups, and node graphs const std::string NODEGRAPH_ENTRY = "Node Graph"; // Filter nodedefs and add to menu if matches filter for (auto node : _nodesToAdd) { // Filter out list of nodes if (subs.size() > 0) { ImGui::SetNextWindowSizeConstraints(ImVec2(250.0f, 300.0f), ImVec2(-1.0f, 500.0f)); std::string str(node.getName()); std::string nodeName = node.getName(); // Disallow creating nested nodegraphs if (_isNodeGraph && node.getGroup() == NODEGRAPH_ENTRY) { continue; } // Allow spaces to be used to search for node names std::replace(subs.begin(), subs.end(), ' ', '_'); if (str.find(subs) != std::string::npos) { if (ImGui::MenuItem(getUserNodeDefName(nodeName).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter))) { addNode(node.getCategory(), getUserNodeDefName(nodeName), node.getType()); _addNewNode = true; memset(input, '\0', sizeof(input)); } } } else { ImGui::SetNextWindowSizeConstraints(ImVec2(100, 10), ImVec2(-1, 300)); if (ImGui::BeginMenu(node.getGroup().c_str())) { ImGui::SetWindowFontScale(_fontScale); std::string name = node.getName(); std::string prefix = "ND_"; if (name.compare(0, prefix.size(), prefix) == 0 && name.compare(prefix.size(), std::string::npos, node.getCategory()) == 0) { if (ImGui::MenuItem(getUserNodeDefName(name).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter))) { addNode(node.getCategory(), getUserNodeDefName(name), node.getType()); _addNewNode = true; } } else { if (ImGui::BeginMenu(node.getCategory().c_str())) { if (ImGui::MenuItem(getUserNodeDefName(name).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter))) { addNode(node.getCategory(), getUserNodeDefName(name), node.getType()); _addNewNode = true; } ImGui::EndMenu(); } } ImGui::EndMenu(); } } } ImGui::EndPopup(); open_AddPopup = false; } } void Graph::searchNodePopup(bool cursor) { const bool open_search = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::IsKeyDown(ImGuiKey_F) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl); if (open_search) { cursor = true; ImGui::OpenPopup("search"); } if (ImGui::BeginPopup("search")) { ed::NavigateToSelection(); static ImGuiTextFilter filter; ImGui::Text("Search for Node:"); static char input[16]{ "" }; ImGui::SameLine(); if (cursor) { ImGui::SetKeyboardFocusHere(); } ImGui::InputText("##input", input, sizeof(input)); if (std::string(input).size() > 0) { for (UiNodePtr node : _graphNodes) { if (node->getName().find(std::string(input)) != std::string::npos) { if (ImGui::MenuItem(node->getName().c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter))) { _searchNodeId = node->getId(); memset(input, '\0', sizeof(input)); } } } } ImGui::EndPopup(); } } bool Graph::isPinHovered() { ed::PinId currentPin = ed::GetHoveredPin(); ed::PinId nullPin = 0; return currentPin != nullPin; } void Graph::addPinPopup() { // Add a floating popup to pin when hovered if (isPinHovered()) { ed::Suspend(); UiPinPtr pin = getPin(ed::GetHoveredPin()); std::string connected; std::string value; if (pin->_connected) { mx::StringVec connectedNames; for (UiPinPtr connectedPin : pin->getConnections()) { connectedNames.push_back(connectedPin->_name); } connected = "\nConnected to " + mx::joinStrings(connectedNames, ", "); } else if (pin->_input) { value = "\nValue: " + pin->_input->getValueString(); } const std::string message("Name: " + pin->_name + "\nType: " + pin->_type + value + connected); ImGui::SetTooltip("%s", message.c_str()); ed::Resume(); } } void Graph::readOnlyPopup() { if (_popup) { ImGui::SetNextWindowSize(ImVec2(200, 100)); ImGui::OpenPopup("Read Only"); _popup = false; } if (ImGui::BeginPopup("Read Only")) { ImGui::Text("This graph is Read Only"); ImGui::EndPopup(); } } void Graph::shaderPopup() { if (_renderer->getMaterialCompilation()) { ImGui::SetNextWindowPos(ImVec2((float) _renderer->getViewWidth() - 135, (float) _renderer->getViewHeight() + 5)); ImGui::SetNextWindowBgAlpha(80.f); ImGui::OpenPopup("Shaders"); } if (ImGui::BeginPopup("Shaders")) { ImGui::Text("Compiling Shaders"); if (!_renderer->getMaterialCompilation()) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } void Graph::handleRenderViewInputs() { ImVec2 mousePos = ImGui::GetMousePos(); mx::Vector2 mxMousePos = mx::Vector2(mousePos.x, mousePos.y); float scrollAmt = ImGui::GetIO().MouseWheel; int button = -1; bool down = false; if (ImGui::IsMouseDragging(0) || ImGui::IsMouseDragging(1)) { _renderer->setMouseMotionEvent(mxMousePos); } if (ImGui::IsMouseClicked(0)) { button = 0; down = true; _renderer->setMouseButtonEvent(button, down, mxMousePos); } else if (ImGui::IsMouseClicked(1)) { button = 1; down = true; _renderer->setMouseButtonEvent(button, down, mxMousePos); } else if (ImGui::IsMouseReleased(0)) { button = 0; _renderer->setMouseButtonEvent(button, down, mxMousePos); } else if (ImGui::IsMouseReleased(1)) { button = 1; _renderer->setMouseButtonEvent(button, down, mxMousePos); } else if (ImGui::IsKeyPressed(ImGuiKey_KeypadAdd)) { _renderer->setKeyEvent(ImGuiKey_KeypadAdd); } else if (ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract)) { _renderer->setKeyEvent(ImGuiKey_KeypadSubtract); } // Scrolling not possible if open or save file dialog is open if (scrollAmt != 0 && !_fileDialogSave.isOpened() && !_fileDialog.isOpened() && !_fileDialogGeom.isOpened()) { _renderer->setScrollEvent(scrollAmt); } } void Graph::drawGraph(ImVec2 mousePos) { if (_searchNodeId > 0) { ed::SelectNode(_searchNodeId); ed::NavigateToSelection(); _searchNodeId = -1; } bool TextCursor = false; // Center imgui window and set size ImGuiIO& io2 = ImGui::GetIO(); ImGui::SetNextWindowSize(io2.DisplaySize); ImGui::SetNextWindowPos(ImVec2(io2.DisplaySize.x * 0.5f, io2.DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::Begin("MaterialX", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings); io2.ConfigFlags = ImGuiConfigFlags_IsSRGB | ImGuiConfigFlags_NavEnableKeyboard; io2.MouseDoubleClickTime = .5; graphButtons(); ed::Begin("My Editor"); { ed::Suspend(); // Set up popups for adding a node when tab is pressed ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); ImGui::SetNextWindowSizeConstraints(ImVec2(250.0f, 300.0f), ImVec2(-1.0f, 500.0f)); addNodePopup(TextCursor); searchNodePopup(TextCursor); addPinPopup(); readOnlyPopup(); ImGui::PopStyleVar(); ed::Resume(); // Gather selected nodes / links - from ImGui Node Editor blueprints-example.cpp std::vector selectedNodes; std::vector selectedLinks; selectedNodes.resize(ed::GetSelectedObjectCount()); selectedLinks.resize(ed::GetSelectedObjectCount()); int nodeCount = ed::GetSelectedNodes(selectedNodes.data(), static_cast(selectedNodes.size())); int linkCount = ed::GetSelectedLinks(selectedLinks.data(), static_cast(selectedLinks.size())); selectedNodes.resize(nodeCount); selectedLinks.resize(linkCount); if (io2.KeyCtrl && io2.MouseDown[0]) { _ctrlClick = true; } // Set current node based off of selected node if (selectedNodes.size() > 0) { int graphPos = findNode(int(selectedNodes[0].Get())); if (graphPos > -1) { // Only selected if its not the same as previously selected if (!_prevUiNode || (_prevUiNode->getName() != _graphNodes[graphPos]->getName())) { _currUiNode = _graphNodes[graphPos]; // Update render material if needed if (_currUiNode->getNode()) { setRenderMaterial(_currUiNode); } else if (_currUiNode->getNodeGraph() || _currUiNode->getOutput()) { setRenderMaterial(_currUiNode); } _prevUiNode = _currUiNode; } } } // Check if keyboard shortcuts for copy/cut/paste have been used if (ed::BeginShortcut()) { if (ed::AcceptCopy()) { _copiedNodes.clear(); for (ed::NodeId selected : selectedNodes) { int pos = findNode((int) selected.Get()); if (pos >= 0) { _copiedNodes.insert(std::pair(_graphNodes[pos], nullptr)); } } } else if (ed::AcceptCut()) { if (!readOnly()) { _copiedNodes.clear(); // Same as copy but remove from graphNodes for (ed::NodeId selected : selectedNodes) { int pos = findNode((int) selected.Get()); if (pos >= 0) { _copiedNodes.insert(std::pair(_graphNodes[pos], nullptr)); } } _isCut = true; } else { _popup = true; } } else if (ed::AcceptPaste()) { if (!readOnly()) { for (auto iter = _copiedNodes.begin(); iter != _copiedNodes.end(); ++iter) { copyUiNode(iter->first); _addNewNode = true; } } else { _popup = true; } } } // Set y-position of first node std::vector outputNum = createNodes(_isNodeGraph); // Address copy information if applicable and relink graph if a new node has been added if (_addNewNode) { copyInputs(); linkGraph(); ImVec2 canvasPos = ed::ScreenToCanvas(mousePos); // Place the copied nodes or the individual new nodes if (!_copiedNodes.empty()) { positionPasteBin(canvasPos); } else if (!_graphNodes.empty()) { ed::SetNodePosition(_graphNodes.back()->getId(), canvasPos); } _copiedNodes.clear(); _addNewNode = false; } // Layout and link graph during the initial call of drawGraph if (_initial || _autoLayout) { _currLinks.clear(); float y = 0.f; _levelMap = std::unordered_map>(); // Start layout with output or material nodes since layout algorithm works right to left for (int outN : outputNum) { layoutPosition(_graphNodes[outN], ImVec2(1200.f, y), true, 0); y += 350; } // If there are no output or material nodes but the nodes have position layout each individual node if (_graphNodes.size() > 0) { if (outputNum.size() == 0 && _graphNodes[0]->getElement()) { for (UiNodePtr node : _graphNodes) { layoutPosition(node, ImVec2(0, 0), true, 0); } } } linkGraph(); findYSpacing(0.f); layoutInputs(); // Automatically frame node graph upon loading ed::NavigateToContent(); } if (_delete) { linkGraph(); _delete = false; } connectLinks(); // Set to false after initial layout so that nodes can be moved _initial = false; _autoLayout = false; // Start the session with content centered if (ImGui::GetFrameCount() == 2) { ed::NavigateToContent(0.0f); } // Delete selected nodes and their links if delete key is pressed // or if the shortcut for cut is used if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow)) { if (ImGui::IsKeyReleased(ImGuiKey_Delete) || _isCut) { if (selectedNodes.size() > 0) { _frameCount = ImGui::GetFrameCount(); _renderer->setMaterialCompilation(true); for (ed::NodeId id : selectedNodes) { if (int(id.Get()) > 0) { int pos = findNode(int(id.Get())); if (pos >= 0 && !readOnly()) { deleteNode(_graphNodes[pos]); _delete = true; ed::DeselectNode(id); ed::DeleteNode(id); _currUiNode = nullptr; } else if (readOnly()) { _popup = true; } } } linkGraph(); } _isCut = false; } // Hotkey to frame selected node(s) if (ImGui::IsKeyReleased(ImGuiKey_F) && !_fileDialogSave.isOpened()) { ed::NavigateToSelection(); } // Go back up from inside a subgraph if (ImGui::IsKeyReleased(ImGuiKey_U) && (!ImGui::IsPopupOpen("add node")) && (!ImGui::IsPopupOpen("search")) && !_fileDialogSave.isOpened()) { upNodeGraph(); } } // Add new link if (ed::BeginCreate()) { ed::PinId startPinId, endPinId, filterPinId; if (ed::QueryNewLink(&startPinId, &endPinId)) { if (!readOnly()) { addLink(startPinId, endPinId); } else { _popup = true; } } if (ed::QueryNewNode(&filterPinId)) { if (getPin(filterPinId)->_type != "null") { _pinFilterType = getPin(filterPinId)->_type; } } } else { _pinFilterType = mx::EMPTY_STRING; } ed::EndCreate(); // Delete link if (ed::BeginDelete()) { ed::LinkId deletedLinkId; while (ed::QueryDeletedLink(&deletedLinkId)) { if (!readOnly()) { deleteLink(deletedLinkId); } else { _popup = true; } } } ed::EndDelete(); } // Dive into a node that has a subgraph ed::NodeId clickedNode = ed::GetDoubleClickedNode(); if (clickedNode.Get() > 0) { if (_currUiNode != nullptr) { if (_currUiNode->getNode() != nullptr) { mx::InterfaceElementPtr impl = _currUiNode->getNode()->getImplementation(); // Only dive if current node is a node graph if (impl && impl->isA()) { savePosition(); _graphStack.push(_graphNodes); _pinStack.push(_currPins); _sizeStack.push(_graphTotalSize); mx::NodeGraphPtr implGraph = impl->asA(); _initial = true; _graphNodes.clear(); ed::DeselectNode(_currUiNode->getId()); _currUiNode = nullptr; _currGraphElem = implGraph; if (readOnly()) { std::string graphName = implGraph->getName() + " (Read Only)"; _currGraphName.push_back(graphName); _popup = true; } else { _currGraphName.push_back(implGraph->getName()); } buildUiNodeGraph(implGraph); ed::NavigateToContent(); } } else if (_currUiNode->getNodeGraph() != nullptr) { savePosition(); _graphStack.push(_graphNodes); _pinStack.push(_currPins); _sizeStack.push(_graphTotalSize); mx::NodeGraphPtr implGraph = _currUiNode->getNodeGraph(); _initial = true; _graphNodes.clear(); _isNodeGraph = true; setRenderMaterial(_currUiNode); ed::DeselectNode(_currUiNode->getId()); _currUiNode = nullptr; _currGraphElem = implGraph; if (readOnly()) { std::string graphName = implGraph->getName() + " (Read Only)"; _currGraphName.push_back(graphName); _popup = true; } else { _currGraphName.push_back(implGraph->getName()); } buildUiNodeGraph(implGraph); ed::NavigateToContent(); } } } shaderPopup(); if (ImGui::GetFrameCount() == (_frameCount + 2)) { updateMaterials(); _renderer->setMaterialCompilation(false); } ed::Suspend(); _fileDialogSave.display(); // Save file if (_fileDialogSave.hasSelected()) { std::string message; if (!_graphDoc->validate(&message)) { std::cerr << "*** Validation warnings for " << _materialFilename.getBaseName() << " ***" << std::endl; std::cerr << message; } _materialFilename = _fileDialogSave.getSelected(); ed::Resume(); savePosition(); saveDocument(_materialFilename); _fileDialogSave.clearSelected(); } else { ed::Resume(); } ed::End(); ImGui::End(); _fileDialog.display(); // Create and load document from selected file if (_fileDialog.hasSelected()) { mx::FilePath fileName = _fileDialog.getSelected(); _currGraphName.clear(); std::string graphName = fileName.getBaseName(); _currGraphName.push_back(graphName.substr(0, graphName.length() - 5)); _graphDoc = loadDocument(fileName); _initial = true; buildUiBaseGraph(_graphDoc); _currGraphElem = _graphDoc; _prevUiNode = nullptr; _fileDialog.clearSelected(); _renderer->setDocument(_graphDoc); _renderer->updateMaterials(nullptr); } _fileDialogGeom.display(); if (_fileDialogGeom.hasSelected()) { mx::FilePath fileName = _fileDialogGeom.getSelected(); _fileDialogGeom.clearSelected(); _renderer->loadMesh(fileName); _renderer->updateMaterials(nullptr); } _fileDialogImage.display(); } int Graph::findNode(int nodeId) { int count = 0; for (size_t i = 0; i < _graphNodes.size(); i++) { if (_graphNodes[i]->getId() == nodeId) { return count; } count++; } return -1; } bool Graph::edgeExists(UiEdge newEdge) { if (_currEdge.size() > 0) { for (UiEdge edge : _currEdge) { if (edge.getDown()->getId() == newEdge.getDown()->getId()) { if (edge.getUp()->getId() == newEdge.getUp()->getId()) { if (edge.getInput() == newEdge.getInput()) { return true; } } } else if (edge.getUp()->getId() == newEdge.getDown()->getId()) { if (edge.getDown()->getId() == newEdge.getUp()->getId()) { if (edge.getInput() == newEdge.getInput()) { return true; } } } } } else { return false; } return false; } bool Graph::linkExists(Link newLink) { for (const auto& link : _currLinks) { if (link._startAttr == newLink._startAttr) { if (link._endAttr == newLink._endAttr) { return true; } } else if (link._startAttr == newLink._endAttr) { if (link._endAttr == newLink._startAttr) { return true; } } } return false; } void Graph::savePosition() { for (UiNodePtr node : _graphNodes) { mx::ElementPtr elem = node->getElement(); if (elem) { ImVec2 pos = ed::GetNodePosition(node->getId()); pos.x /= DEFAULT_NODE_SIZE.x; pos.y /= DEFAULT_NODE_SIZE.y; elem->setAttribute(mx::Element::XPOS_ATTRIBUTE, std::to_string(pos.x)); elem->setAttribute(mx::Element::YPOS_ATTRIBUTE, std::to_string(pos.y)); if (elem->hasAttribute("nodedef")) { elem->removeAttribute("nodedef"); } } } } void Graph::saveDocument(mx::FilePath filePath) { if (filePath.getExtension() != mx::MTLX_EXTENSION) { filePath.addExtension(mx::MTLX_EXTENSION); } mx::DocumentPtr writeDoc = _graphDoc; // If requested, create a modified version of the document for saving. if (!_saveNodePositions) { writeDoc = _graphDoc->copy(); for (mx::ElementPtr elem : writeDoc->traverseTree()) { elem->removeAttribute(mx::Element::XPOS_ATTRIBUTE); elem->removeAttribute(mx::Element::YPOS_ATTRIBUTE); } } mx::XmlWriteOptions writeOptions; writeOptions.elementPredicate = getElementPredicate(); mx::writeToXmlFile(writeDoc, filePath, &writeOptions); }