From 50a029ebe584ba537bcbae547b468348da618f9c Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Thu, 29 May 2025 12:19:21 -0400 Subject: [PATCH 1/5] First trial to capture graph editor. --- source/MaterialXGraphEditor/Graph.cpp | 172 +++++++++++++++++++++++++- source/MaterialXGraphEditor/Graph.h | 7 ++ 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index 7cb28b9d68..8a3fdc2517 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -3276,12 +3276,12 @@ void Graph::graphButtons() } // 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); + splitter(true, 4.0f, &_leftPaneWidth, &_rightPaneWidth, 20.0f, 20.0f); + //std::cout << "Left pane width: " << _leftPaneWidth << std::endl; + //std::cout << "Right pane width: " << _rightPaneWidth << std::endl; // Create back button and graph hierarchy name display - ImGui::Indent(leftPaneWidth + 15.f); + ImGui::Indent(_leftPaneWidth + _leftPanelIndent); if (ImGui::Button("<")) { upNodeGraph(); @@ -3301,12 +3301,12 @@ void Graph::graphButtons() } } ImVec2 windowPos2 = ImGui::GetWindowPos(); - ImGui::Unindent(leftPaneWidth + 15.f); + ImGui::Unindent(_leftPaneWidth + _leftPanelIndent); ImGui::PopStyleColor(); ImGui::NewLine(); // Create two windows using splitter - float paneWidth = (leftPaneWidth - 2.0f); + float paneWidth = (_leftPaneWidth - 2.0f); float aspectRatio = _renderer->getPixelRatio(); ImVec2 screenSize = ImVec2(paneWidth, paneWidth / aspectRatio); @@ -4031,6 +4031,147 @@ void Graph::handleRenderViewInputs() } } +mx::ImagePtr Graph::getFrameImage() const +{ + + // Capture the graph editor window to an image file +#if 0 + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 windowSize = ImGui::GetWindowSize(); + int x = static_cast(windowPos.x); + int y = static_cast(windowPos.y); + int w = static_cast(windowSize.x); + int h = static_cast(windowSize.y); + + mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), + (unsigned int)(h), 3); + frameImage->createResourceBuffer(); + + glFlush(); + glReadPixels(0, 0, frameImage->getWidth(), frameImage->getHeight(), GL_RGB, GL_UNSIGNED_BYTE, image->getResourceBuffer()); + +#elif 0 + // 1. Get the current right pane dimensions (accounts for resizing) + ImVec2 rightPanePos = ImGui::GetWindowPos(); + rightPanePos.x += leftPaneWidth + 4.0f; // Adjust for left pane + splitter thickness + + ImVec2 rightPaneSize = ImGui::GetContentRegionAvail(); // Current available space + rightPaneSize.x = rightPaneWidth; // Use the dynamic right pane width + + ImVec2 capturePos = rightPanePos; + capturePos.y = ImGui::GetIO().DisplaySize.y - rightPanePos.y - rightPaneSize.y; + + int w = static_cast(rightPaneSize.x); + int h = static_cast(rightPaneSize.y); + mx::ImagePtr frameImage = mx::Image::create((unsigned int) (w), + (unsigned int) (h), 3); + frameImage->createResourceBuffer(); + + glFlush(); + glReadPixels( + (int)capturePos.x, + (int)capturePos.y, + (int)rightPaneSize.x, + (int)rightPaneSize.y, + GL_RGB, + GL_UNSIGNED_BYTE, + frameImage->getResourceBuffer() + ); +#elif 1 + + // 1. Get the current window and splitter state + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 windowPos = window->Pos; + ImVec2 windowSize = window->Size; + std::cout << "Window Position: " << windowPos.x << ", " << windowPos.y << std::endl; + std::cout << "Window Size: " << windowSize.x << ", " << windowSize.y << std::endl; + ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); // Get the content region min position + ImVec2 contentMax = ImGui::GetWindowContentRegionMax(); // Get the content region max position + std::cout << "Content Region Min: " << contentMin.x << ", " << contentMin.y << std::endl; + std::cout << "Content Region Max: " << contentMax.x << ", " << contentMax.y << std::endl; + ImVec2 displaySize = ImGui::GetContentRegionAvail(); // Excludes menu bars + std::cout << "Content Size: " << displaySize.x << ", " << displaySize.y << std::endl; + + // 2. Calculate the ACTUAL right pane dimensions (not just the stored variables) + ImVec2 rightPanePos = windowPos; + rightPanePos.x += _leftPaneWidth + _leftPanelIndent + _leftPanelIndent; // leftPane + splitter width + + ImVec2 rightPaneSize; + rightPaneSize.x = windowSize.x - (_leftPaneWidth + _leftPanelIndent + (2.0f *_leftPanelIndent)); // dynamic width + + //rightPaneSize.y = windowSize.y - (windowPos.y - ImGui::GetCursorStartPos().y + 30.0f); // content height + //rightPaneSize.y = windowSize.y - (windowPos.y + 30.0f); // content height + rightPaneSize.y = displaySize.y - (windowPos.y + 15.0f); // content height + + std::cout << "Right Pane Position: " << rightPanePos.x << ", " << rightPanePos.y << std::endl; + std::cout << "Right Pane Size: " << rightPaneSize.x << ", " << rightPaneSize.y << std::endl; + + // 3. Get the actual viewport coordinates (accounting for DPI scaling) + //ImVec2 displaySize = ImGui::GetIO().DisplaySize; + ImVec2 capturePos = rightPanePos; + //capturePos.y = displaySize.y - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL + capturePos.y = displaySize.y - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL + std::cout << "Capture Position: " << capturePos.x << ", " << capturePos.y << std::endl; + + int w = static_cast(rightPaneSize.x); + int h = static_cast(rightPaneSize.y); + mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), + (unsigned int)(h), 3); + frameImage->createResourceBuffer(); + + glFlush(); + glReadPixels( + (int)capturePos.x, + (int)0/*(capturePos.y*/, + (int)rightPaneSize.x, + (int)rightPaneSize.y, + GL_RGB, + GL_UNSIGNED_BYTE, + frameImage->getResourceBuffer() + ); +#else + // 1. Get the main viewport (accounts for menu bars/toolbars) + ImGuiViewport* viewport = ImGui::GetMainViewport(); + + // 2. Calculate the right pane's content area + ImVec2 rightPaneStart = ImGui::GetWindowPos(); + rightPaneStart.x += _leftPaneWidth + 15.0f; // After left pane + splitter + + ImVec2 contentSize = ImGui::GetContentRegionAvail(); // Excludes menu bars + ImVec2 rightPaneEnd = rightPaneStart + contentSize; + + // 3. Adjust for viewport offsets (multi-monitor/DPI aware) + rightPaneStart -= viewport->Pos; + rightPaneEnd -= viewport->Pos; + + // 4. Calculate capture dimensions + int captureWidth = (int)(rightPaneEnd.x - rightPaneStart.x); + int captureHeight = (int)(rightPaneEnd.y - rightPaneStart.y); + + int w = static_cast(captureWidth); + int h = static_cast(captureHeight); + mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), + (unsigned int)(h), 3); + frameImage->createResourceBuffer(); + + // 5. Read pixels (with OpenGL Y flip) + //std::vector pixels(captureWidth * captureHeight * 4); + glReadPixels( + (int)rightPaneStart.x, + (int)(viewport->Size.y - rightPaneEnd.y), // Flip Y coordinate + captureWidth, + captureHeight, + GL_RGB, + GL_UNSIGNED_BYTE, + frameImage->getResourceBuffer() + ); + +#endif + + return frameImage; +} + + void Graph::drawGraph(ImVec2 mousePos) { if (_searchNodeId > 0) @@ -4050,8 +4191,26 @@ void Graph::drawGraph(ImVec2 mousePos) io2.ConfigFlags = ImGuiConfigFlags_IsSRGB | ImGuiConfigFlags_NavEnableKeyboard; io2.MouseDoubleClickTime = .5; + + // Capture. Do before anything else to avoid window position changes from later UI calls + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_B)) + { + mx::ImagePtr frameImage = getFrameImage(); + static bool showFrameSavedPopup = false; + std::string filename = "graph_capture.png"; + if (frameImage && _renderer->getImageHandler()->saveImage(filename, frameImage, true)) + { + std::cout << "Frame saved to: " << filename << std::endl; + } + else + { + std::cout << "Failed to write frame to disk: " << filename << std::endl; + } + } + graphButtons(); + ed::Begin("My Editor"); { ed::Suspend(); @@ -4365,6 +4524,7 @@ void Graph::drawGraph(ImVec2 mousePos) } } ed::EndDelete(); + } // Dive into a node that has a subgraph diff --git a/source/MaterialXGraphEditor/Graph.h b/source/MaterialXGraphEditor/Graph.h index ff4e93b189..0a01090603 100644 --- a/source/MaterialXGraphEditor/Graph.h +++ b/source/MaterialXGraphEditor/Graph.h @@ -236,6 +236,8 @@ class Graph void showHelp() const; + mx::ImagePtr getFrameImage() const; + private: mx::StringVec _geomFilter; mx::StringVec _mtlxFilter; @@ -326,6 +328,11 @@ class Graph // Options bool _saveNodePositions; + + float _leftPaneWidth = 375.0f; + float _leftPanelIndent = 15.0f; + float _rightPaneWidth = 750.0f; + }; #endif From a7c9885b9c843a6110ca366bd074ba4551674086 Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Thu, 29 May 2025 14:24:38 -0400 Subject: [PATCH 2/5] Tweak capture. Fix build. --- source/MaterialXGraphEditor/Graph.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index 8a3fdc2517..be3359df46 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -4094,10 +4094,11 @@ mx::ImagePtr Graph::getFrameImage() const // 2. Calculate the ACTUAL right pane dimensions (not just the stored variables) ImVec2 rightPanePos = windowPos; - rightPanePos.x += _leftPaneWidth + _leftPanelIndent + _leftPanelIndent; // leftPane + splitter width + rightPanePos.x += _leftPaneWidth + _leftPanelIndent + 4;// +_leftPanelIndent; // leftPane + splitter width + rightPanePos.y += _leftPanelIndent; ImVec2 rightPaneSize; - rightPaneSize.x = windowSize.x - (_leftPaneWidth + _leftPanelIndent + (2.0f *_leftPanelIndent)); // dynamic width + rightPaneSize.x = windowSize.x - (_leftPaneWidth + _leftPanelIndent + (_leftPanelIndent)); // dynamic width //rightPaneSize.y = windowSize.y - (windowPos.y - ImGui::GetCursorStartPos().y + 30.0f); // content height //rightPaneSize.y = windowSize.y - (windowPos.y + 30.0f); // content height @@ -4110,7 +4111,7 @@ mx::ImagePtr Graph::getFrameImage() const //ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 capturePos = rightPanePos; //capturePos.y = displaySize.y - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL - capturePos.y = displaySize.y - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL + capturePos.y = displaySize.y + 4 - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL std::cout << "Capture Position: " << capturePos.x << ", " << capturePos.y << std::endl; int w = static_cast(rightPaneSize.x); @@ -4122,7 +4123,7 @@ mx::ImagePtr Graph::getFrameImage() const glFlush(); glReadPixels( (int)capturePos.x, - (int)0/*(capturePos.y*/, + (int)0,//capturePos.y, (int)rightPaneSize.x, (int)rightPaneSize.y, GL_RGB, @@ -4196,7 +4197,6 @@ void Graph::drawGraph(ImVec2 mousePos) if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_B)) { mx::ImagePtr frameImage = getFrameImage(); - static bool showFrameSavedPopup = false; std::string filename = "graph_capture.png"; if (frameImage && _renderer->getImageHandler()->saveImage(filename, frameImage, true)) { From 7bb3835ced1ec7091f762b9c9f459fe526f10b82 Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Thu, 29 May 2025 15:11:47 -0400 Subject: [PATCH 3/5] Clip top. --- source/MaterialXGraphEditor/Graph.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index be3359df46..dd81868907 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -4115,7 +4115,7 @@ mx::ImagePtr Graph::getFrameImage() const std::cout << "Capture Position: " << capturePos.x << ", " << capturePos.y << std::endl; int w = static_cast(rightPaneSize.x); - int h = static_cast(rightPaneSize.y); + int h = static_cast(rightPaneSize.y) - 15; mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), (unsigned int)(h), 3); frameImage->createResourceBuffer(); @@ -4123,9 +4123,9 @@ mx::ImagePtr Graph::getFrameImage() const glFlush(); glReadPixels( (int)capturePos.x, - (int)0,//capturePos.y, + (int)capturePos.y - 11, (int)rightPaneSize.x, - (int)rightPaneSize.y, + (int)rightPaneSize.y - 15, GL_RGB, GL_UNSIGNED_BYTE, frameImage->getResourceBuffer() From 7412715463e50b107c9426b1301c75bbe3252be3 Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Fri, 30 May 2025 11:27:42 -0400 Subject: [PATCH 4/5] Cleanup code. - Add in save dialog and help. - Ad in CTRL-I vs CTRL-SHIFT-I to capture editor or whole window. --- source/MaterialXGraphEditor/Graph.cpp | 248 +++++++++++--------------- source/MaterialXGraphEditor/Graph.h | 11 +- 2 files changed, 114 insertions(+), 145 deletions(-) diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index dd81868907..2b2db9266b 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -143,6 +143,7 @@ Graph::Graph(const std::string& materialFilename, _initial(false), _delete(false), _fileDialogSave(FileDialog::EnterNewFilename), + _fileDialogSnapshot(FileDialog::EnterNewFilename), _isNodeGraph(false), _graphTotalSize(0), _popup(false), @@ -802,6 +803,10 @@ void Graph::setRenderMaterial(UiNodePtr node) for (const std::string& testPath : testPaths) { mx::ElementPtr testElem = _graphDoc->getDescendant(testPath); + if (!testElem) + { + continue; + } mx::NodePtr testNode = testElem->asA(); std::vector downstreamPorts; if (testNode) @@ -3180,6 +3185,40 @@ void Graph::saveGraphToFile() _fileDialogSave.open(); } +void Graph::showSnapshotDialog() +{ + if (!_capturedImage) + { + return; + } + mx::StringVec imageFilter; + imageFilter.push_back(".png"); + _fileDialogSnapshot.setTypeFilters(imageFilter); + _fileDialogSnapshot.setTitle("Save SnapshotAs"); + _fileDialogSnapshot.open(); +} + +void Graph::saveSnapshotToFile() +{ + ed::Suspend(); + _fileDialogSnapshot.display(); + + // Save capture + if (_fileDialogSnapshot.hasSelected()) + { + std::string captureName = _fileDialogSnapshot.getSelected(); + std::cout << "Save image to: " << captureName << std::endl; + _renderer->getImageHandler()->saveImage(captureName, _capturedImage, true); + _capturedImage = nullptr; + ed::Resume(); + _fileDialogSnapshot.clearSelected(); + } + else + { + ed::Resume(); + } +} + void Graph::loadGeometry() { _fileDialogGeom.setTitle("Load Geometry"); @@ -3281,7 +3320,7 @@ void Graph::graphButtons() //std::cout << "Right pane width: " << _rightPaneWidth << std::endl; // Create back button and graph hierarchy name display - ImGui::Indent(_leftPaneWidth + _leftPanelIndent); + ImGui::Indent(_leftPaneWidth + _nodeEditorIndent); if (ImGui::Button("<")) { upNodeGraph(); @@ -3301,7 +3340,7 @@ void Graph::graphButtons() } } ImVec2 windowPos2 = ImGui::GetWindowPos(); - ImGui::Unindent(_leftPaneWidth + _leftPanelIndent); + ImGui::Unindent(_leftPaneWidth + _nodeEditorIndent); ImGui::PopStyleColor(); ImGui::NewLine(); @@ -3735,6 +3774,8 @@ void Graph::showHelp() const 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::BulletText("CTRL-I : Capture Editor Panel to disk."); + ImGui::BulletText("CTRL-SHIFT-I : Capture entire window to disk."); ImGui::TreePop(); } } @@ -4031,147 +4072,64 @@ void Graph::handleRenderViewInputs() } } -mx::ImagePtr Graph::getFrameImage() const +mx::ImagePtr Graph::performSnapshot(bool captureWindow) const { + if (captureWindow) + { + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 windowSize = ImGui::GetWindowSize(); + unsigned int w = static_cast(windowSize.x); + unsigned int h = static_cast(windowSize.y); - // Capture the graph editor window to an image file -#if 0 - ImVec2 windowPos = ImGui::GetWindowPos(); - ImVec2 windowSize = ImGui::GetWindowSize(); - int x = static_cast(windowPos.x); - int y = static_cast(windowPos.y); - int w = static_cast(windowSize.x); - int h = static_cast(windowSize.y); - - mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), - (unsigned int)(h), 3); - frameImage->createResourceBuffer(); - - glFlush(); - glReadPixels(0, 0, frameImage->getWidth(), frameImage->getHeight(), GL_RGB, GL_UNSIGNED_BYTE, image->getResourceBuffer()); - -#elif 0 - // 1. Get the current right pane dimensions (accounts for resizing) - ImVec2 rightPanePos = ImGui::GetWindowPos(); - rightPanePos.x += leftPaneWidth + 4.0f; // Adjust for left pane + splitter thickness - - ImVec2 rightPaneSize = ImGui::GetContentRegionAvail(); // Current available space - rightPaneSize.x = rightPaneWidth; // Use the dynamic right pane width - - ImVec2 capturePos = rightPanePos; - capturePos.y = ImGui::GetIO().DisplaySize.y - rightPanePos.y - rightPaneSize.y; - - int w = static_cast(rightPaneSize.x); - int h = static_cast(rightPaneSize.y); - mx::ImagePtr frameImage = mx::Image::create((unsigned int) (w), - (unsigned int) (h), 3); - frameImage->createResourceBuffer(); - - glFlush(); - glReadPixels( - (int)capturePos.x, - (int)capturePos.y, - (int)rightPaneSize.x, - (int)rightPaneSize.y, - GL_RGB, - GL_UNSIGNED_BYTE, - frameImage->getResourceBuffer() - ); -#elif 1 - - // 1. Get the current window and splitter state + mx::ImagePtr image = mx::Image::create(w, h, 3); + if (image) + { + image->createResourceBuffer(); + + glFlush(); + glReadPixels(0, 0, image->getWidth(), image->getHeight(), GL_RGB, GL_UNSIGNED_BYTE, image->getResourceBuffer()); + } + return image; + } + + // Get main window information ImGuiWindow* window = ImGui::GetCurrentWindow(); ImVec2 windowPos = window->Pos; ImVec2 windowSize = window->Size; - std::cout << "Window Position: " << windowPos.x << ", " << windowPos.y << std::endl; - std::cout << "Window Size: " << windowSize.x << ", " << windowSize.y << std::endl; - ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); // Get the content region min position - ImVec2 contentMax = ImGui::GetWindowContentRegionMax(); // Get the content region max position - std::cout << "Content Region Min: " << contentMin.x << ", " << contentMin.y << std::endl; - std::cout << "Content Region Max: " << contentMax.x << ", " << contentMax.y << std::endl; - ImVec2 displaySize = ImGui::GetContentRegionAvail(); // Excludes menu bars - std::cout << "Content Size: " << displaySize.x << ", " << displaySize.y << std::endl; - - // 2. Calculate the ACTUAL right pane dimensions (not just the stored variables) - ImVec2 rightPanePos = windowPos; - rightPanePos.x += _leftPaneWidth + _leftPanelIndent + 4;// +_leftPanelIndent; // leftPane + splitter width - rightPanePos.y += _leftPanelIndent; - ImVec2 rightPaneSize; - rightPaneSize.x = windowSize.x - (_leftPaneWidth + _leftPanelIndent + (_leftPanelIndent)); // dynamic width - - //rightPaneSize.y = windowSize.y - (windowPos.y - ImGui::GetCursorStartPos().y + 30.0f); // content height - //rightPaneSize.y = windowSize.y - (windowPos.y + 30.0f); // content height - rightPaneSize.y = displaySize.y - (windowPos.y + 15.0f); // content height + // Get editor information + ImVec2 contentSize = ImGui::GetContentRegionAvail(); // Excludes menu bars - std::cout << "Right Pane Position: " << rightPanePos.x << ", " << rightPanePos.y << std::endl; - std::cout << "Right Pane Size: " << rightPaneSize.x << ", " << rightPaneSize.y << std::endl; + float splitterSize = 4.0f; + float leftOffset = _leftPaneWidth + _nodeEditorIndent; + float topOffset = _nodeEditorIndent; + float rightOffset = _nodeEditorIndent; - // 3. Get the actual viewport coordinates (accounting for DPI scaling) - //ImVec2 displaySize = ImGui::GetIO().DisplaySize; - ImVec2 capturePos = rightPanePos; - //capturePos.y = displaySize.y - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL - capturePos.y = displaySize.y + 4 - rightPanePos.y - rightPaneSize.y; // Flip Y for OpenGL - std::cout << "Capture Position: " << capturePos.x << ", " << capturePos.y << std::endl; + // Calculate the right pane position. Include splitter and indentation from top left corner + ImVec2 rightPanePos; + rightPanePos.x = leftOffset + splitterSize; + rightPanePos.y = topOffset; + + // Calculate the right panel size. Remove left and right editor offsets. + ImVec2 rightPaneSize; + rightPaneSize.x = windowSize.x - (leftOffset + rightOffset); + rightPaneSize.y = contentSize.y - (windowPos.y + 2.0f * topOffset); int w = static_cast(rightPaneSize.x); - int h = static_cast(rightPaneSize.y) - 15; - mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), - (unsigned int)(h), 3); - frameImage->createResourceBuffer(); - - glFlush(); - glReadPixels( - (int)capturePos.x, - (int)capturePos.y - 11, - (int)rightPaneSize.x, - (int)rightPaneSize.y - 15, - GL_RGB, - GL_UNSIGNED_BYTE, - frameImage->getResourceBuffer() - ); -#else - // 1. Get the main viewport (accounts for menu bars/toolbars) - ImGuiViewport* viewport = ImGui::GetMainViewport(); - - // 2. Calculate the right pane's content area - ImVec2 rightPaneStart = ImGui::GetWindowPos(); - rightPaneStart.x += _leftPaneWidth + 15.0f; // After left pane + splitter - - ImVec2 contentSize = ImGui::GetContentRegionAvail(); // Excludes menu bars - ImVec2 rightPaneEnd = rightPaneStart + contentSize; - - // 3. Adjust for viewport offsets (multi-monitor/DPI aware) - rightPaneStart -= viewport->Pos; - rightPaneEnd -= viewport->Pos; - - // 4. Calculate capture dimensions - int captureWidth = (int)(rightPaneEnd.x - rightPaneStart.x); - int captureHeight = (int)(rightPaneEnd.y - rightPaneStart.y); - - int w = static_cast(captureWidth); - int h = static_cast(captureHeight); - mx::ImagePtr frameImage = mx::Image::create((unsigned int)(w), - (unsigned int)(h), 3); - frameImage->createResourceBuffer(); - - // 5. Read pixels (with OpenGL Y flip) - //std::vector pixels(captureWidth * captureHeight * 4); - glReadPixels( - (int)rightPaneStart.x, - (int)(viewport->Size.y - rightPaneEnd.y), // Flip Y coordinate - captureWidth, - captureHeight, - GL_RGB, - GL_UNSIGNED_BYTE, - frameImage->getResourceBuffer() - ); - -#endif - - return frameImage; -} + int h = static_cast(rightPaneSize.y); + mx::ImagePtr image = mx::Image::create(static_cast(w), static_cast(h), 3); + if (image) + { + image->createResourceBuffer(); + glFlush(); + glReadPixels(static_cast(rightPanePos.x), 0, w, h, + GL_RGB, GL_UNSIGNED_BYTE, + image->getResourceBuffer() + ); + } + return image; +} void Graph::drawGraph(ImVec2 mousePos) { @@ -4193,24 +4151,26 @@ void Graph::drawGraph(ImVec2 mousePos) io2.ConfigFlags = ImGuiConfigFlags_IsSRGB | ImGuiConfigFlags_NavEnableKeyboard; io2.MouseDoubleClickTime = .5; - // Capture. Do before anything else to avoid window position changes from later UI calls - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_B)) + // Capture. Perform before other UI calls which can change window size / positioning. + if (io2.KeyCtrl && ImGui::IsKeyReleased(ImGuiKey_I)) { - mx::ImagePtr frameImage = getFrameImage(); - std::string filename = "graph_capture.png"; - if (frameImage && _renderer->getImageHandler()->saveImage(filename, frameImage, true)) - { - std::cout << "Frame saved to: " << filename << std::endl; - } - else + // Capture entire window if SHIFT, otherwise capture node editor + bool captureWindow = io2.KeyShift; + _capturedImage = performSnapshot(captureWindow); + //std::string filename = "graph_capture.png"; + if (_capturedImage) { - std::cout << "Failed to write frame to disk: " << filename << std::endl; + //std::cout << "Frame saved to: " << filename << std::endl; + showSnapshotDialog(); } + //else + //{ + // std::cout << "Failed to write frame to disk: " << filename << std::endl; + //} } graphButtons(); - ed::Begin("My Editor"); { ed::Suspend(); @@ -4627,6 +4587,8 @@ void Graph::drawGraph(ImVec2 mousePos) ed::Resume(); } + saveSnapshotToFile(); + ed::End(); ImGui::End(); diff --git a/source/MaterialXGraphEditor/Graph.h b/source/MaterialXGraphEditor/Graph.h index 0a01090603..04d4862bea 100644 --- a/source/MaterialXGraphEditor/Graph.h +++ b/source/MaterialXGraphEditor/Graph.h @@ -236,7 +236,10 @@ class Graph void showHelp() const; - mx::ImagePtr getFrameImage() const; + // Methods to capture snapshot to file + mx::ImagePtr performSnapshot(bool captureWindow = false) const; + void showSnapshotDialog(); + void saveSnapshotToFile(); private: mx::StringVec _geomFilter; @@ -297,6 +300,7 @@ class Graph FileDialog _fileDialogSave; FileDialog _fileDialogImage; FileDialog _fileDialogGeom; + FileDialog _fileDialogSnapshot; std::string _fileDialogImageInputName; bool _isNodeGraph; @@ -329,10 +333,13 @@ class Graph // Options bool _saveNodePositions; + // Panel information float _leftPaneWidth = 375.0f; - float _leftPanelIndent = 15.0f; + float _nodeEditorIndent = 15.0f; float _rightPaneWidth = 750.0f; + // Image capture + mx::ImagePtr _capturedImage; }; #endif From 202c43468f5dc4f89d5eafafcc3f59b5be6bf159 Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Fri, 30 May 2025 11:58:37 -0400 Subject: [PATCH 5/5] Cleanup. --- source/MaterialXGraphEditor/Graph.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index 2b2db9266b..7cb9430bea 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -3316,8 +3316,6 @@ void Graph::graphButtons() // Split window into panes for NodeEditor splitter(true, 4.0f, &_leftPaneWidth, &_rightPaneWidth, 20.0f, 20.0f); - //std::cout << "Left pane width: " << _leftPaneWidth << std::endl; - //std::cout << "Right pane width: " << _rightPaneWidth << std::endl; // Create back button and graph hierarchy name display ImGui::Indent(_leftPaneWidth + _nodeEditorIndent); @@ -4157,16 +4155,10 @@ void Graph::drawGraph(ImVec2 mousePos) // Capture entire window if SHIFT, otherwise capture node editor bool captureWindow = io2.KeyShift; _capturedImage = performSnapshot(captureWindow); - //std::string filename = "graph_capture.png"; if (_capturedImage) { - //std::cout << "Frame saved to: " << filename << std::endl; showSnapshotDialog(); } - //else - //{ - // std::cout << "Failed to write frame to disk: " << filename << std::endl; - //} } graphButtons();