Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewer Snapshot to Catalogue #5533

Merged
merged 2 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
1.3.x.x (relative to 1.3.7.0)
=======

Features
--------

- Viewer : Added "Snapshot To Catalogue" command to the right-click menu of the 3D view.

Fixes
-----

- Windows `Scene/OpenGL/Shader` Menu : Removed `\` at the beginning of menu items.

API
---

- SceneGadget : Added `snapshotToFile()` method.

1.3.7.0 (relative to 1.3.6.1)
=======

Expand Down
2 changes: 1 addition & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ libraries = {

"GafferSceneUI" : {
"envAppends" : {
"LIBS" : [ "Gaffer", "GafferUI", "GafferImage", "GafferImageUI", "GafferScene", "Iex$IMATH_LIB_SUFFIX", "IECoreGL$CORTEX_LIB_SUFFIX", "IECoreImage$CORTEX_LIB_SUFFIX", "IECoreScene$CORTEX_LIB_SUFFIX" ],
"LIBS" : [ "Gaffer", "GafferUI", "GafferImage", "GafferImageUI", "GafferScene", "Iex$IMATH_LIB_SUFFIX", "IECoreGL$CORTEX_LIB_SUFFIX", "IECoreImage$CORTEX_LIB_SUFFIX", "IECoreScene$CORTEX_LIB_SUFFIX", "OpenImageIO$OIIO_LIB_SUFFIX", "OpenImageIO_Util$OIIO_LIB_SUFFIX" ],
},
"pythonEnvAppends" : {
"LIBS" : [ "IECoreGL$CORTEX_LIB_SUFFIX", "GafferBindings", "GafferScene", "GafferImage", "GafferUI", "GafferImageUI", "GafferSceneUI", "IECoreScene$CORTEX_LIB_SUFFIX" ],
Expand Down
8 changes: 8 additions & 0 deletions include/GafferSceneUI/Private/OutputBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

#include "IECore/PathMatcher.h"

#include <filesystem>
#include <mutex>

namespace GafferSceneUI
Expand Down Expand Up @@ -81,6 +82,13 @@ class OutputBuffer
using BufferChangedSignal = Gaffer::Signals::Signal<void()>;
BufferChangedSignal &bufferChangedSignal();

/// See `SceneGadget::snapshotToFile()` for documentation.
void snapshotToFile(
const std::filesystem::path &fileName,
const Imath::Box2f &resolutionGate = Imath::Box2f(),
const IECore::CompoundData *metadata = nullptr
);

private :

class DisplayDriver;
Expand Down
13 changes: 13 additions & 0 deletions include/GafferSceneUI/SceneGadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@

#include "IECoreGL/State.h"

#include <filesystem>

namespace GafferSceneUI
{

Expand Down Expand Up @@ -191,6 +193,17 @@ class GAFFERSCENEUI_API SceneGadget : public GafferUI::Gadget
/// Implemented to return the name of the object under the mouse.
std::string getToolTip( const IECore::LineSegment3f &line ) const override;

/// Saves a snapshot of the current rendered scene. All renderers are supported _except_
/// the OpenGL renderer. All formats supported by OpenImageIO can be used. The output
/// display window will be set to `resolutionGate` if it is not an empty `Box2f`.
/// All of the supplied metadata will be written, regardless of conflicts with
/// OpenImageIO built-in metadata.
void snapshotToFile(
const std::filesystem::path &fileName,
const Imath::Box2f &resolutionGate = Imath::Box2f(),
const IECore::CompoundData *metadata = nullptr
) const;

protected :

void renderLayer( Layer layer, const GafferUI::Style *style, RenderReason reason ) const override;
Expand Down
109 changes: 109 additions & 0 deletions python/GafferSceneUI/SceneViewUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
##########################################################################

import functools
import datetime
import pathlib

import imath

Expand All @@ -46,6 +48,7 @@
import GafferUI
import GafferScene
import GafferSceneUI
import GafferImage

from ._SceneViewInspector import _SceneViewInspector

Expand Down Expand Up @@ -1082,13 +1085,119 @@ def __appendClippingPlaneMenuItems( menuDefinition, prefix, view, parentWidget )
}
)

def __snapshotDescription( view ) :

sceneGadget = view.viewportGadget().getPrimaryChild()
if sceneGadget.getRenderer() == "OpenGL" :
return "Viewport snapshots are only available for rendered (non-OpenGL) previews."

return "Snapshot viewport and send to catalogue."

def __snapshotToCatalogue( catalogue, view ) :

timeStamp = str( datetime.datetime.now() )

scriptRoot = view["in"].getInput().ancestor( Gaffer.ScriptNode )

fileName = pathlib.Path(
scriptRoot.context().substitute( catalogue["directory"].getValue() )
) / ( f"viewerSnapshot-{ timeStamp.replace( ':', '-' ).replace(' ', '_' ) }.exr" )

resolutionGate = imath.Box3f()
if isinstance( view, GafferSceneUI.SceneView ) :
resolutionGate = view.resolutionGate()

metadata = IECore.CompoundData(
{
"gaffer:sourceScene" : view["in"].getInput().relativeName( scriptRoot ),
"gaffer:context:frame" : view["in"].node().getContext().getFrame()
}
)

sceneGadget = view.viewportGadget().getPrimaryChild()
sceneGadget.snapshotToFile( fileName, resolutionGate, metadata )

image = GafferImage.Catalogue.Image( "Snapshot1", Gaffer.Plug.Direction.In, Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
image["fileName"].setValue( fileName )

image["description"].setValue(
"Snapshot of {} at frame {} taken at {}".format(
metadata["gaffer:sourceScene"],
metadata["gaffer:context:frame"], timeStamp[:-6] # Remove trailing microseconds
)
)

catalogue["images"].source().addChild( image )
johnhaddon marked this conversation as resolved.
Show resolved Hide resolved
catalogue["imageIndex"].source().setValue( len( catalogue["images"].source().children() ) - 1 )

def __snapshotCataloguesSubMenu( view, scriptNode ) :

menuDefinition = IECore.MenuDefinition()

catalogueList = list( GafferImage.Catalogue.RecursiveRange( scriptNode ) )

if len( catalogueList ) == 0 :
menuDefinition.append(
"/No Catalogues Available",
{
"active" : False,
}
)

else :
commonDescription = __snapshotDescription( view )
commonActive = view["renderer"]["name"].getValue() != "OpenGL"

for c in catalogueList :

cName = c["name"].getValue()
nName = c["imageIndex"].source().node().relativeName( scriptNode )

snapshotActive = (
commonActive and
not Gaffer.MetadataAlgo.readOnly( c["images"].source() ) and
not Gaffer.MetadataAlgo.readOnly( c["imageIndex"].source() )
)

snapshotDescription = commonDescription
if Gaffer.MetadataAlgo.readOnly( c["images"].source() ) :
snapshotDescription = "\"images\" plug is read-only"
if Gaffer.MetadataAlgo.readOnly( c["imageIndex"].source() ) :
snapshotDescription = "\"imageIndex\" plug is read-only"

menuDefinition.append(
"/" + ( nName + ( " ({})".format( cName ) if cName else "" ) ),
{
"active" : snapshotActive,
johnhaddon marked this conversation as resolved.
Show resolved Hide resolved
"command" : functools.partial( __snapshotToCatalogue, c, view ),
"description" : snapshotDescription
}
)

return menuDefinition


def __viewContextMenu( viewer, view, menuDefinition ) :

if not isinstance( view, GafferSceneUI.SceneView ) :
return False

__appendClippingPlaneMenuItems( menuDefinition, "/Clipping Planes", view, viewer )

scriptNode = view["in"].getInput().ancestor( Gaffer.ScriptNode )

menuDefinition.append(
"/Snapshot to Catalogue",
{
"subMenu" : functools.partial(
__snapshotCataloguesSubMenu,
view,
scriptNode
)
}
)


GafferUI.Viewer.viewContextMenuSignal().connect( __viewContextMenu, scoped = False )

def __plugValueWidgetContextMenu( menuDefinition, plugValueWidget ) :
Expand Down
43 changes: 43 additions & 0 deletions src/GafferSceneUI/OutputBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "IECoreGL/ShaderLoader.h"

#include "IECoreImage/DisplayDriver.h"
#include "IECoreImage/OpenImageIOAlgo.h"

#include "OpenEXR/OpenEXRConfig.h"
#if OPENEXR_VERSION_MAJOR < 3
Expand All @@ -45,6 +46,8 @@
#include "Imath/ImathBoxAlgo.h"
#endif

#include "OpenImageIO/imageio.h"

#include "boost/lexical_cast.hpp"

#include <unordered_set>
Expand Down Expand Up @@ -462,6 +465,46 @@ void OutputBuffer::dirtyTexture()
}
}

void OutputBuffer::snapshotToFile(
const std::filesystem::path &fileName,
const Box2f &resolutionGate,
const CompoundData *metadata
)
{
std::filesystem::create_directories( fileName.parent_path() );

std::unique_lock lock( m_bufferReallocationMutex );

OIIO::ImageSpec spec( m_dataWindow.size().x + 1, m_dataWindow.size().y + 1, 4, OIIO::TypeDesc::HALF );

if( !resolutionGate.isEmpty() )
{
spec.x = -resolutionGate.min.x;
spec.y = -resolutionGate.min.y;

spec.full_x = 0;
spec.full_y = 0;
spec.full_width = resolutionGate.size().x;
spec.full_height = resolutionGate.size().y;
}

const std::vector<float> &rgbaBuffer = m_rgbaBuffer;

for( const auto &[key, value] : metadata->readable() )
{
const IECoreImage::OpenImageIOAlgo::DataView dataView( value.get() );
if( dataView.data )
{
spec.attribute( key.c_str(), dataView.type, dataView.data );
}
}

std::unique_ptr<OIIO::ImageOutput> output = OIIO::ImageOutput::create( fileName.c_str() );
output->open( fileName.c_str(), spec );
output->write_image( OIIO::TypeDesc::FLOAT, &rgbaBuffer[0] );
output->close();
}

//////////////////////////////////////////////////////////////////////////
// DisplayDriver
//////////////////////////////////////////////////////////////////////////
Expand Down
14 changes: 14 additions & 0 deletions src/GafferSceneUI/SceneGadget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,20 @@ Imath::Box3f SceneGadget::bound() const
return bound( /* selection = */ false );
}

void SceneGadget::snapshotToFile(
const std::filesystem::path &fileName,
const Box2f &resolutionGate,
const CompoundData *metadata
) const
{
if( !m_outputBuffer )
{
return;
}

m_outputBuffer->snapshotToFile( fileName, resolutionGate, metadata );
}

void SceneGadget::renderLayer( Layer layer, const GafferUI::Style *style, RenderReason reason ) const
{
assert( layer == m_layer || layer == Layer::MidFront );
Expand Down
11 changes: 11 additions & 0 deletions src/GafferSceneUIModule/SceneGadgetBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ Imath::Box3f bound( SceneGadget &g, bool selected, const IECore::PathMatcher *om
return g.bound( selected, omitted );
}

void snapshotToFile( SceneGadget &g, const std::filesystem::path &fileName, const Imath::Box2f &resolutionGate, const IECore::CompoundData *metadata )
{
ScopedGILRelease gilRelease;
g.snapshotToFile( fileName, resolutionGate, metadata );
}

} // namespace

void GafferSceneUIModule::bindSceneGadget()
Expand Down Expand Up @@ -214,6 +220,11 @@ void GafferSceneUIModule::bindSceneGadget()
.def( "getSelection", &SceneGadget::getSelection, return_value_policy<copy_const_reference>() )
.def( "selectionBound", &selectionBound )
.def( "bound", &bound, ( arg( "selected" ), arg( "omitted" ) = object() ) )
.def(
"snapshotToFile",
&snapshotToFile,
( arg( "fileName" ), arg( "resolutionGate" ) = Imath::Box2f(), arg( "metadata" ) = object() )
)
;

enum_<SceneGadget::State>( "State" )
Expand Down
Loading