Viewport Screenshot

   18074   19   7
User Avatar
Member
24 posts
Joined: Dec. 2015
Offline
Is it possible to save what is currently in the active viewport using Python? Like flipbook for just one frame. I cannot find a way to do this and would really appreciate help.
User Avatar
Member
538 posts
Joined: Dec. 2006
Offline
Create shelf tool with script:

from time import gmtime, strftime

# GET VIEWPORT CAMERA PATH
cur_desktop = hou.ui.curDesktop()
desktop = cur_desktop.name()
panetab = cur_desktop.paneTabOfType(hou.paneTabType.SceneViewer).name()
persp = cur_desktop.paneTabOfType(hou.paneTabType.SceneViewer).curViewport().name()
camera_path = desktop + “.” + panetab + “.” + “world” “.” + persp

# BUILD DEFAULT FILE NAME FROM CURRENT TIME
default_filename = strftime(“screenshot_%d_%b_%Y_%H_%M_%S.jpg”, gmtime())

# SELECT FILE
filename = hou.ui.selectFile( title='Select Screenshot File', default_value=default_filename, file_type=hou.fileType.Image )

# WRITE TO FILE
if filename is not None:
frame = hou.frame()
hou.hscript( “viewwrite -f %d %d %s ‘%s’” % (frame, frame, camera_path, filename) )
https://gumroad.com/alexeyvanzhula [gumroad.com]
User Avatar
Member
24 posts
Joined: Dec. 2015
Offline
Thanks a million vux! That's exactly what I was looking for!
User Avatar
Member
146 posts
Joined: June 2016
Offline
That is cool trick to know.. thanks for sharing Vux!
Mohan Pugaz
movfx
https://www.instagram.com/movfx/ [www.instagram.com]
https://www.youtube.com/channel/@_movfx
User Avatar
Staff
3463 posts
Joined: July 2005
Offline
you can look through the python code that builds the Pose Library as well.
Michael Goldfarb | www.odforce.net
Training Lead
SideFX
www.sidefx.com
User Avatar
Staff
635 posts
Joined: July 2005
Offline
elveatles
Is it possible to save what is currently in the active viewport using Python? Like flipbook for just one frame. I cannot find a way to do this and would really appreciate help.

There is also something called an “OpenGL ROP” in Houdini. I don't think it's quite what you want in this particular case, but it may serve you well in other instances when you just need a quick render of your geometry.

Cristin
User Avatar
Member
146 posts
Joined: June 2016
Offline
vux
Create shelf tool with script:

from time import gmtime, strftime

# GET VIEWPORT CAMERA PATH
cur_desktop = hou.ui.curDesktop()
desktop = cur_desktop.name()
panetab = cur_desktop.paneTabOfType(hou.paneTabType.SceneViewer).name()
persp = cur_desktop.paneTabOfType(hou.paneTabType.SceneViewer).curViewport().name()
camera_path = desktop + “.” + panetab + “.” + “world” “.” + persp

# BUILD DEFAULT FILE NAME FROM CURRENT TIME
default_filename = strftime(“screenshot_%d_%b_%Y_%H_%M_%S.jpg”, gmtime())

# SELECT FILE
filename = hou.ui.selectFile( title='Select Screenshot File', default_value=default_filename, file_type=hou.fileType.Image )

# WRITE TO FILE
if filename is not None:
frame = hou.frame()
hou.hscript( “viewwrite -f %d %d %s ‘%s’” % (frame, frame, camera_path, filename) )






For all of you who try to use VUX's script.

I copy pasted this script and it doesnt work at first.
Because there is something wrong with the double quotes,
I just deleted the quotes and retyped them that works perfectly

Thanks a lot to VUX for this script its really useful for me
Mohan Pugaz
movfx
https://www.instagram.com/movfx/ [www.instagram.com]
https://www.youtube.com/channel/@_movfx
User Avatar
Staff
5205 posts
Joined: July 2005
Offline
Note that H16.0 added HOM support for flipbooking directly, so that you could do the entire script in HOM now without the hscript(“viewwrite”) command.

scene = cur_desktop.paneTabOfType(hou.paneTabType.SceneViewer)
flip_options = scene.flipbookSettings().stash()
flip_options.frameRange( (frame, frame) )
flip_options.output(filename)
scene.flipbook(scene.curViewport(), flip_options)
User Avatar
Member
146 posts
Joined: June 2016
Offline
Really cool to know , thanks for the useful info and code
Mohan Pugaz
movfx
https://www.instagram.com/movfx/ [www.instagram.com]
https://www.youtube.com/channel/@_movfx
User Avatar
Member
4700 posts
Joined: Feb. 2012
Online
Can we now both launch the flipbook window and also save the result on disk in H16? Before it wasn't possible. For example auto save flipbook on disk while also showing it interactively flipbooking using mplay?
Senior FX TD @ Industrial Light & Magic
Get to the NEXT level in Houdini & VEX with Pragmatic VEX! [www.pragmatic-vfx.com]

youtube.com/@pragmaticvfx | patreon.com/animatrix | pragmaticvfx.gumroad.com
User Avatar
Staff
5205 posts
Joined: July 2005
Offline
You could write a HOM script to do that, interleaving writing to disk and ip for each frame, but otherwise no.
User Avatar
Member
4700 posts
Joined: Feb. 2012
Online
Thanks Mark that's what I wanted to do. I was told before it couldn't be done.
ID #72062

I thought once I launch flipbook, I can't control mplay anymore. Any idea how would this look like in pseudo code?
Senior FX TD @ Industrial Light & Magic
Get to the NEXT level in Houdini & VEX with Pragmatic VEX! [www.pragmatic-vfx.com]

youtube.com/@pragmaticvfx | patreon.com/animatrix | pragmaticvfx.gumroad.com
User Avatar
Member
8 posts
Joined: Sept. 2017
Offline
animatrix3d
Thanks Mark that's what I wanted to do. I was told before it couldn't be done.
ID #72062

I thought once I launch flipbook, I can't control mplay anymore. Any idea how would this look like in pseudo code?

I also want to do this, do you have any ideas or code can solve this problems
User Avatar
Member
899 posts
Joined: Feb. 2016
Offline
Hello, I have to bump this old discussion.

I tried the code, which works super fine, but it popups the save dialog.
I need it to automatically write the viewport.jpg to disk, as fast as possible, as my trying to do a batch export of the crude geometry in the viewport. No need fancy stuff like AA, or shadows. Headlight only.

Ideally it should save the viewport_$F.jpg just after houdini cooks the frame. (so no save sequence)

I had a look at the hou.FlipbookSettings class [www.sidefx.com] , but couldn't find any method to automatically skip the save dialog, and write the file to disk.

thanks for any advice!
User Avatar
Member
899 posts
Joined: Feb. 2016
Offline
Should I go this way maybe?
https://pypi.org/project/pyscreenshot/ [pypi.org]
External module to take screenshots and save them to disk.



*Edit*
I guess I should use the openGL render node.
Edited by Andr - Nov. 13, 2018 16:21:33
User Avatar
Member
8041 posts
Joined: Sept. 2011
Offline
There's no save dialog when flipbooking directly to a path. I use a qt gui that initiates flipbooks through python, and this is not an issue. Perhaps you are not passing a flipbooksettings object?
User Avatar
Member
899 posts
Joined: Feb. 2016
Offline
oh thanks, now I get it.
I was still using hou.ui.selectFile() from the previous code.

cheers
User Avatar
Member
380 posts
Joined: July 2005
Offline
This was a tad annoying for me in H19.0.455 as the MPlay window would launch by default whenever I invoked the render -- until I discovered that outputToMplay() is True by default.

import traceback
from os.path import dirname,basename,splitext

def getSceneViewer():
    """
    return an instance of a visible viewport. 
    There may be many, some could be closed, any visible are current
    """
    panetabs = hou.ui.paneTabs()
    panetabs = [x for x in panetabs if x.type() == hou.paneTabType.SceneViewer]
    panetabs = sorted(panetabs, key=lambda x: x.isCurrentTab())
    if panetabs:
        return panetabs[-1]
    else:
        print("No SceneViewers detected.")
        return None
    
   
def viewwrite():
    """
    Get the current sceneviewer (may be more than one or hidden)
    and screengrab the perspective viewport to a file in the 
    publish location to be picked up with the publish. 
    
    Note that .png output will render pooly, so use .jpg   
    """
    try:
        node = hou.node(".")
        path = node.parm("outputpath").eval()       
        file,ext = splitext(basename(path))
        filename = f'{dirname(path)}/{file}.jpg'
        frame = hou.frame()
    
        desktop = hou.ui.curDesktop()
        sceneview = getSceneViewer()
        viewport = sceneview.curViewport()
    
        # this will open an mplay window to show the result
        fbs = sceneview.flipbookSettings().stash()
        fbs.frameRange( (frame, frame) )
        fbs.output(filename)
        fbs.outputToMPlay(False)
        sceneview.flipbook(viewport, fbs)
            
    except Exception as e:
        print(f'ERROR: Failed to Capture Viewport\n{traceback.format_exc(2)}')
        
User Avatar
Member
66 posts
Joined: May 2019
Offline
This can be also done with 10 lines of Python.

There's a saveThumbnailFromViewer()function that ships with Houdini in husdassetutils
At this location: C:\Program Files\Side Effects Software\Houdini 20.5.365\houdini\python3.11libs\husd

The whole code (without force cooking Python SOP):

from husd import assetutils

viewer = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer)

assetutils.saveThumbnailFromViewer(
    sceneviewer = viewer,
    frame=1,
    res=(512,512),
    output="$HIP/textures/thumbnail.png"
)


Attachments:
explorer_o9XLOXmEbc.gif (7.7 MB)

Generalist. Transforming still images to 3D animation 🔮
Socials: https://linktr.ee/AnimGraphLab [linktr.ee]
User Avatar
Member
275 posts
Joined: Sept. 2012
Offline
I built a Null pipeline helper

One function it could do is to make you capture your object and place that preview next to the null , hooked, in your network...

Doing similar thing in an HDA panel is more difficult, i should give another try...



Here is the python code :

import hou
import os
import subprocess
import time
from time import gmtime, strftime
from pathlib import Path
from sys import platform

widthRatio = 4  # Change to make the screenshot bigger or smaller, this is ~x4 node width

def takeScreenShot(savePath):
    ''' Take screenshot with a region select tool for different platforms '''
    Path(os.path.dirname(Path(savePath))).mkdir(parents=True, exist_ok=True)
    
    if platform == "linux" or platform == "linux2":
        savepath = os.path.dirname(Path(savePath))
        screenshotPNG = subprocess.check_output('mv "$(xfce4-screenshooter -ro ls)" -v ' + savepath, shell=True)
        screenshotPNG = screenshotPNG.decode().strip().split('\n')[0].split(' -> ')[1].replace("'", "")
        os.chdir(savepath)
        os.rename(os.path.basename(screenshotPNG), os.path.basename(savePath))
    elif platform == "darwin":
        pass  # Add Mac-specific screenshot logic here if needed
    elif platform == "win32":
        subprocess.check_call([r"C:\Users\vincent\Downloads\MiniCap\MiniCap.exe", "-captureregselect", "-save", savePath, "-exit"])

    # Ensure the screenshot has been saved and the file exists
    if not os.path.exists(savePath):
        raise FileNotFoundError(f"Screenshot not saved at: {savePath}")

def wait_for_image(path, timeout=10):
    ''' Waits until the screenshot image is available '''
    elapsed = 0
    while not os.path.exists(path) and elapsed < timeout:
        time.sleep(0.5)  # Wait 0.5 seconds before checking again
        elapsed += 0.5
    if not os.path.exists(path):
        raise FileNotFoundError(f"Image not found after waiting {timeout} seconds: {path}")

def removeBackgroundImage(**kwargs):
    ''' Removes background image from the network editor '''
    deletingNode = [x[1] for x in kwargs.items()][0]
    image = deletingNode.userData('houdinipath')
    editor = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
    backgroundImagesDic = editor.backgroundImages()
    backgroundImagesDic = tuple(x for x in backgroundImagesDic if x.path() != image)
    editor.setBackgroundImages(backgroundImagesDic)

def changeBackgroundImageBrightness(event_type, **kwargs):
    ''' Changes brightness/visibility if template or bypass flags are checked '''
    nullNode = [x[1] for x in kwargs.items()][0]
    image = nullNode.userData('houdinipath')
    brightness = 1.0
    if nullNode.isBypassed():
        brightness = 0.0
    elif nullNode.isTemplateFlagSet():
        brightness = 0.5
    editor = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
    backgroundImagesDic = editor.backgroundImages()
    for i, item in enumerate(backgroundImagesDic):
        if item.path() == image:
            backgroundImagesDic[i].setBrightness(brightness)
            break
    editor.setBackgroundImages(backgroundImagesDic)

# Generate unique path for screenshot
timestamp = strftime('%Y%m%d_%H%M%S', gmtime())
hipname = str(hou.getenv('HIPNAME'))
hippath = str(hou.getenv('HIP')) + '/screenshots'
screenshotName = hipname + '.' + timestamp + '.png'
systempath = os.path.join(hippath, screenshotName)
houdinipath = f'$HIP/screenshots/{screenshotName}'

# Take screenshot and wait for the file to be created
takeScreenShot(systempath)
wait_for_image(systempath)

# Set up background image plane
editor = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
image = hou.NetworkImage()
image.setPath(houdinipath)

# Create or select null node
sel = hou.selectedNodes()
nullNode = ''
if sel:
    lastSel = sel[-1]
    nullNode = lastSel.parent().createNode('null', 'screenshot')
    if lastSel.outputConnections():
        nullNode.setInput(0, lastSel)
else:
    nullNode = editor.pwd().createNode('null', 'screenshot')
    nullNode.moveToGoodPosition()
    lastSel = nullNode

# Configure image plane placement
nullNode.setUserData('nodeshape', 'task')
nullNode.setPosition(lastSel.position())
nullNode.setColor(hou.Color(0.3, 0.3, 0.3))

# Adjusted node movement to reduce offset
nullNode.move([lastSel.size()[0] * 1.5, -lastSel.size()[1] * 1.5])

# Calculate image resolution and placement
rez = hou.imageResolution(systempath)
ratio = 1.0 * rez[1] / rez[0]
rect = hou.BoundingRect(0, -lastSel.size()[1] * 1.05, widthRatio, -widthRatio * ratio - lastSel.size()[1] * 1.05)
image.setRelativeToPath(nullNode.path())
image.setRect(rect)

# Store the image path as user data instead of a parameter
nullNode.setUserData('houdinipath', houdinipath)

# Attach a function that deletes the background image plane if the corresponding node is deleted
nullNode.addEventCallback((hou.nodeEventType.BeingDeleted,), removeBackgroundImage)

# Attach a function to change visibility or opacity if corresponding node flags are changed
nullNode.addEventCallback((hou.nodeEventType.FlagChanged,), changeBackgroundImageBrightness)

# Add image to network background
backgroundImagesDic = editor.backgroundImages()
backgroundImagesDic = backgroundImagesDic + (image,)
editor.setBackgroundImages(backgroundImagesDic)
Edited by vinyvince - Oct. 2, 2024 10:20:48

Attachments:
Nulll_template_Helper_vince.png (1.1 MB)

Vincent Thomas   (VFX and Art since 1998)
Senior Env and Lighting  artist & Houdini generalist & Creative Concepts
http://fr.linkedin.com/in/vincentthomas [fr.linkedin.com]
  • Quick Links