Different Gravity
Different Gravity

Reputation: 163

Extract a Vector Image or High Res image from Nuke's Node Graph

I am a VFX Teacher and currently when I want to grab images of Node Graphs to use in Lecture slides I have to make the Node Graph full screen and do a screen capture, but as you can imagine with larger scripts I have to zoom out so far that sometimes its not recognisable.

It looks to me the way Nuke's Node Graph resizes when you zoom in and out, that's its probably a Vector image of some kind under the hood. I am looking for a way either export this image so I can get Higher Res version of the whole node graph. Either as Vector, or just a higher res rasterized image.

Does anyone know if there might be a way to do this with Python? Or is there some external script that can do this?

Upvotes: 3

Views: 673

Answers (1)

Erwan Leroy
Erwan Leroy

Reputation: 310

I have wanted this for a long time, but it took your question to get me looking into it a bit deeper.

I don't believe there is an actual vector image available under the hood (it's openGL under the hood), but I also don't see why there wouldn't be a way to automate a full-res screenshot.

I have written a script that will check the size of your node graph, set the zoom to 100% and screenshot every piece of the DAG, then stitch it automatically. It takes a few seconds because if I try to do it too fast things get mixed up due to the threading, so I had to introduce artificial pauses in between each screenshot.

You should be able to run the code below. Don't forget to set your path on the second to last line!

from PySide2 import QtWidgets, QtOpenGL, QtGui
from math import ceil
import time

 
def get_dag():
    stack = QtWidgets.QApplication.topLevelWidgets()
    while stack:
        widget = stack.pop()
        if widget.objectName() == 'DAG.1':
            for c in widget.children():
                if isinstance(c, QtOpenGL.QGLWidget):
                    return c
        stack.extend(c for c in widget.children() if c.isWidgetType())

def grab_dag(dag, path):
    dag.updateGL()  # This does some funky back and forth but function grabs the wrong thing without it
    pix = dag.grabFrameBuffer()
    pix.save(path)
    
class DagCapture(threading.Thread):
    def __init__(self, path, margins=20, ignore_right=200):
        self.path = path
        threading.Thread.__init__(self)
        self.margins = margins
        self.ignore_right = ignore_right

    def run(self):
        # Store the current dag size and zoom
        original_zoom = nuke.zoom()
        original_center = nuke.center()
        # Calculate the total size of the DAG
        min_x = min([node.xpos() for node in nuke.allNodes()]) - self.margins
        min_y = min([node.ypos() for node in nuke.allNodes()]) - self.margins
        max_x = max([node.xpos() + node.screenWidth() for node in nuke.allNodes()]) + self.margins
        max_y = max([node.ypos() + node.screenHeight() for node in nuke.allNodes()]) + self.margins

        # Get the Dag Widget
        dag = get_dag()
        if not dag:
            raise RuntimeError("Couldn't get DAG widget")

        # Check the size of the current widget, excluding the right side (because of minimap)
        capture_width = dag.width() - self.ignore_right
        capture_height = dag.height()

        # Calculate the number of tiles required to coveral all
        image_width = max_x - min_x
        image_height = max_y - min_y
        horizontal_tiles = int(ceil(image_width / float(capture_width)))
        vertical_tiles = int(ceil(image_height / float(capture_height)))
        # Create a pixmap to store the results
        pixmap = QtGui.QPixmap(image_width, image_height)
        painter = QtGui.QPainter(pixmap)
        painter.setCompositionMode(painter.CompositionMode_SourceOver)
        # Move the dag so that the top left corner is in the top left corner, screenshot, paste in the pixmap, repeat
        for xtile in range(horizontal_tiles):
            left = min_x + capture_width * xtile
            for ytile in range(vertical_tiles):
                top = min_y + capture_height * ytile
                nuke.executeInMainThread(nuke.zoom, (1, (left + (capture_width + self.ignore_right) / 2, top + capture_height / 2)))
                time.sleep(.5)
                nuke.executeInMainThread(grab_dag, (dag, self.path))
                time.sleep(.5)
                screengrab = QtGui.QImage(self.path)
                painter.drawImage(capture_width * xtile, capture_height * ytile, screengrab)
        painter.end()
        pixmap.save(self.path)
        nuke.executeInMainThread(nuke.zoom, (original_zoom, original_center))
        print "Capture Complete"

t = DagCapture("C:\\Users\\erwan\\Downloads\\test.png")
t.start()

I'm sure this could be improved upon but, hopefully, it will save you time already!

Edit: I have now improved this code a bit: https://github.com/herronelou/nuke_dag_capture

Upvotes: 2

Related Questions