diff --git a/Changes.md b/Changes.md index 1e69b4d8f9b..593ba7657aa 100644 --- a/Changes.md +++ b/Changes.md @@ -9,10 +9,17 @@ Breaking Changes 1.5.x.x (relative to 1.5.3.0) ======= +Features +-------- + +- HierarchyView : Added ability to store and recall the Visible Set in named bookmarks that are saved with the script. +- TensorToMesh : Added new ML node for converting a suitable tensor into a 3d mesh. + Improvements ------------ - AttributeEditor : Added "Select Affected Objects" menu item to the "Linked Lights" and Arnold "Shadow Group" columns. +- ScriptNode : Added support for serialising metadata registered on a ScriptNode. Fixes ----- @@ -22,12 +29,15 @@ Fixes - Fixed bugs which prevented edits being made in "Source" scope when there was a downstream edit in an EditScope (#6172). - Fixed warning messages when attempting to disable a non-existent edit. - Fixed warning message which referred to "None" rather than the "Source" scope. +- PythonEditor : Fixed bug preventing values from being inserted when dragging most VectorData types into the PythonEditor. API --- - RenderPassEditor : Added optional `index` argument to `registerOption()` and `registerColumn()`. This can be used to specify the column's position in the UI. - Metadata : Added `targetsWithMetadata()` function, returning all the string targets which match a pattern and have a specific metadata key. +- VisibleSetData : Implemented `save()` and `load()`. +- ScriptNodeAlgo : Added functions for managing VisibleSet bookmarks. 1.5.3.0 (relative to 1.5.2.0) ======= @@ -437,7 +447,10 @@ Build 1.4.15.x (relative to 1.4.15.4) ======== +Fixes +----- +- PythonEditor : Fixed bug preventing values from being inserted when dragging most VectorData types into the PythonEditor. 1.4.15.4 (relative to 1.4.15.3) ======== diff --git a/SConstruct b/SConstruct index c5582dbe8b2..8103c1f9145 100644 --- a/SConstruct +++ b/SConstruct @@ -1119,12 +1119,12 @@ libraries = { "envAppends" : { "CPPPATH" : [ "$ONNX_ROOT/include" ], "LIBPATH" : [ "$ONNX_ROOT/lib" ], - "LIBS" : [ "Gaffer", "GafferImage", "onnxruntime" ], + "LIBS" : [ "Gaffer", "GafferImage", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ], }, "pythonEnvAppends" : { "CPPPATH" : [ "$ONNX_ROOT/include" ], "LIBPATH" : [ "$ONNX_ROOT/lib" ], - "LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime" ], + "LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ], }, "requiredOptions" : [ "ONNX_ROOT" ], }, diff --git a/include/GafferML/TensorToMesh.h b/include/GafferML/TensorToMesh.h new file mode 100644 index 00000000000..950f2733088 --- /dev/null +++ b/include/GafferML/TensorToMesh.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Lucien Fostier. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferML/Export.h" +#include "GafferML/TensorPlug.h" + +#include "GafferScene/ObjectSource.h" +#include "Gaffer/TransformPlug.h" + +namespace GafferML +{ + +class GAFFERML_API TensorToMesh : public GafferScene::ObjectSource +{ + + public : + + explicit TensorToMesh( const std::string &name=defaultName() ); + ~TensorToMesh() override; + + GAFFER_NODE_DECLARE_TYPE( GafferML::TensorToMesh, TensorToMeshTypeId, GafferScene::ObjectSource ); + + TensorPlug *positionTensorPlug(); + const TensorPlug *positionTensorPlug() const; + + TensorPlug *vertexIdsTensorPlug(); + const TensorPlug *vertexIdsTensorPlug() const; + + void affects( const Gaffer::Plug *input, Gaffer::DependencyNode::AffectedPlugsContainer &outputs ) const override; + + protected : + + void hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeSource( const Gaffer::Context *context ) const override; + + private : + + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( TensorToMesh ) + +} // namespace GafferML diff --git a/include/GafferML/TypeIds.h b/include/GafferML/TypeIds.h index 95125b0d2a0..607ea775a98 100644 --- a/include/GafferML/TypeIds.h +++ b/include/GafferML/TypeIds.h @@ -48,6 +48,7 @@ enum TypeId InferenceTypeId = 110455, TensorReaderTypeId = 110456, DataToTensorTypeId = 110457, + TensorToMeshTypeId = 110458, LastTypeId = 110500 }; diff --git a/include/GafferScene/VisibleSetData.h b/include/GafferScene/VisibleSetData.h index 220919efad5..b3758130abd 100644 --- a/include/GafferScene/VisibleSetData.h +++ b/include/GafferScene/VisibleSetData.h @@ -43,7 +43,7 @@ namespace IECore { - IECORE_DECLARE_TYPEDDATA( VisibleSetData, GafferScene::VisibleSet, void, IECore::SimpleDataHolder ); + IECORE_DECLARE_TYPEDDATA( VisibleSetData, GafferScene::VisibleSet, void, IECore::SharedDataHolder ); } // namespace IECore namespace GafferScene diff --git a/include/GafferSceneUI/ScriptNodeAlgo.h b/include/GafferSceneUI/ScriptNodeAlgo.h index 7a743ca4ba3..f91aa183c0a 100644 --- a/include/GafferSceneUI/ScriptNodeAlgo.h +++ b/include/GafferSceneUI/ScriptNodeAlgo.h @@ -123,6 +123,22 @@ GAFFERSCENEUI_API void setCurrentRenderPass( Gaffer::ScriptNode *script, std::st /// Returns the current render pass for the script. GAFFERSCENEUI_API std::string getCurrentRenderPass( const Gaffer::ScriptNode *script ); +/// Visible Set Bookmarks +/// ===================== + +/// Visible Set bookmarks can be used to store named bookmarks containing a VisibleSet as +/// metadata on the ScriptNode, allowing users to bookmark important VisibleSets to be +/// recalled later. + +/// Stores a VisibleSet as a named bookmark for the script. +GAFFERSCENEUI_API void addVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent = true ); +/// Returns the VisibleSet previously bookmarked as `name`. +GAFFERSCENEUI_API GafferScene::VisibleSet getVisibleSetBookmark( const Gaffer::ScriptNode *script, const std::string &name ); +/// Removes the bookmark previously stored as `name`. +GAFFERSCENEUI_API void removeVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name ); +/// Returns the names of all VisibleSet bookmarks stored on `script`. +GAFFERSCENEUI_API std::vector visibleSetBookmarks( const Gaffer::ScriptNode *script ); + } // namespace ScriptNodeAlgo } // namespace GafferSceneUI diff --git a/python/GafferML/__init__.py b/python/GafferML/__init__.py index 707fcbc0d8f..658c0e6ffd2 100644 --- a/python/GafferML/__init__.py +++ b/python/GafferML/__init__.py @@ -39,6 +39,7 @@ __import__( "Gaffer" ) __import__( "GafferImage" ) +__import__( "GafferScene" ) if hasattr( os, "add_dll_directory" ) : os.add_dll_directory( ( pathlib.Path( os.environ["ONNX_ROOT"] ) / "lib" ).resolve() ) diff --git a/python/GafferMLTest/TensorToMeshTest.py b/python/GafferMLTest/TensorToMeshTest.py new file mode 100644 index 00000000000..3d18fe06287 --- /dev/null +++ b/python/GafferMLTest/TensorToMeshTest.py @@ -0,0 +1,147 @@ +########################################################################## +# +# Copyright (c) 2024, Lucien Fostier. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import unittest + +import imath + +import IECore + +import Gaffer +import GafferTest +import GafferScene +import GafferML + +class TensorToMeshTest( GafferTest.TestCase ) : + def testNoInput( self ) : + + node = GafferML.TensorToMesh() + with self.assertRaisesRegex( Gaffer.ProcessException, "Empty Position tensor" ) : + node["out"].object("/tensorMesh"), + + def test( self ) : + + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + self.assertEqual( tensorToMesh["out"].object( "/" ), IECore.NullObject() ) + self.assertEqual( tensorToMesh["out"].transform( "/" ), imath.M44f() ) + self.assertEqual( tensorToMesh["out"].bound( "/" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) ) + self.assertEqual( tensorToMesh["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "tensorMesh" ] ) ) + + self.assertEqual( tensorToMesh["out"].transform( "/tensorMesh" ), imath.M44f() ) + self.assertEqual( tensorToMesh["out"].bound( "/tensorMesh" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) ) + self.assertEqual( tensorToMesh["out"].childNames( "/tensorMesh" ), IECore.InternedStringVectorData() ) + + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + expectedValue = [component for vec in mesh["P"].data for component in (vec.x, vec.y, vec.z)] + + self.assertEqual( expectedValue, points ) + self.assertEqual( mesh.numFaces(), 1 ) + self.assertEqual( mesh.verticesPerFace, IECore.IntVectorData( [ 3 ] ) ) + + def testWrongVertexTensorDimension( self ) : + + points = [ 0.0, 0.0, 0.0 , 1.0, 1.0, 0.0, 1.0, 1.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position tensor number of dimensions" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongVertexDimension( self ) : + + points = [ 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 2 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position dimensions" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testEmptyFaces( self ): + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Empty VertexIds tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongDataTypeVertices( self ): + points = [ 0, 0, 0, 1, 1, 1, 1, 0, 2 ] + position = IECore.IntVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for position tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + + def testWrongDataTypeFaces( self ): + points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ] + position = IECore.FloatVectorData( points ) + positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] ) + vertexIds = IECore.IntVectorData( [ 0, 1, 2 ] ) + vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] ) + tensorToMesh = GafferML.TensorToMesh() + tensorToMesh["position"].setValue( positionTensor ) + tensorToMesh["vertexIds"].setValue( vertexIdsTensor ) + + with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for vertexIds tensor" ): + mesh = tensorToMesh["out"].object( "/tensorMesh" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferMLTest/__init__.py b/python/GafferMLTest/__init__.py index b926385c6b5..ce1bfdb9b7e 100644 --- a/python/GafferMLTest/__init__.py +++ b/python/GafferMLTest/__init__.py @@ -40,6 +40,7 @@ from .InferenceTest import InferenceTest from .ImageToTensorTest import ImageToTensorTest from .TensorToImageTest import TensorToImageTest +from .TensorToMeshTest import TensorToMeshTest if __name__ == "__main__": import unittest diff --git a/python/GafferMLUI/TensorToMeshUI.py b/python/GafferMLUI/TensorToMeshUI.py new file mode 100644 index 00000000000..ce0d9a52da9 --- /dev/null +++ b/python/GafferMLUI/TensorToMeshUI.py @@ -0,0 +1,90 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferML + +import GafferSceneUI + +Gaffer.Metadata.registerNode( + + GafferML.TensorToMesh, + + "description", + """ + Turns input tensors into a mesh. + """, + + + plugs = { + + "position" : [ + + "description", + """ + The input tensor containing the point positions for the mesh to create. Typically this would be connected + to the output of an Inference node that is doing mesh generation/processing. + """, + + "plugValueWidget:type", "", + "nodule:type", "GafferUI::StandardNodule", + + ], + "vertexIds" : [ + + "description", + """ + The input tensor containing the vertex ids for the mesh to create. Typically this would be connected + to the output of an Inference node that is doing mesh generation/processing. + """, + + "plugValueWidget:type", "", + "nodule:type", "GafferUI::StandardNodule", + + ], + + "out" : [ + + "description", + """ + The output mesh. + """, + + ], + + + } +) diff --git a/python/GafferMLUI/__init__.py b/python/GafferMLUI/__init__.py index 446a2d81fac..6b045220cd6 100644 --- a/python/GafferMLUI/__init__.py +++ b/python/GafferMLUI/__init__.py @@ -38,5 +38,6 @@ from . import InferenceUI from . import ImageToTensorUI from . import TensorToImageUI +from . import TensorToMeshUI __import__( "IECore" ).loadConfig( "GAFFER_STARTUP_PATHS", subdirectory = "GafferMLUI" ) diff --git a/python/GafferMLUITest/DocumentationTest.py b/python/GafferMLUITest/DocumentationTest.py index 45b5fdff387..cb89a2f33df 100644 --- a/python/GafferMLUITest/DocumentationTest.py +++ b/python/GafferMLUITest/DocumentationTest.py @@ -37,6 +37,7 @@ import GafferUITest import GafferImage +import GafferScene import GafferML import GafferMLUI @@ -47,7 +48,7 @@ def test( self ) : self.maxDiff = None self.assertNodesAreDocumented( GafferML, - additionalTerminalPlugTypes = ( GafferImage.ImagePlug, ) + additionalTerminalPlugTypes = ( GafferImage.ImagePlug, GafferScene.ScenePlug ) ) if __name__ == "__main__": diff --git a/python/GafferSceneTest/VisibleSetDataTest.py b/python/GafferSceneTest/VisibleSetDataTest.py index 3291698558f..c420253e02d 100644 --- a/python/GafferSceneTest/VisibleSetDataTest.py +++ b/python/GafferSceneTest/VisibleSetDataTest.py @@ -77,6 +77,26 @@ def test( self ) : self.assertEqual( vd2c, vd2 ) self.assertEqual( vd2c.hash(), vd2.hash() ) + def testSerialisation( self ) : + + v = GafferScene.VisibleSet() + v.expansions = IECore.PathMatcher( [ "/a" ] ) + v.inclusions = IECore.PathMatcher( [ "/b" ] ) + v.exclusions = IECore.PathMatcher( [ "/c" ] ) + + d = GafferScene.VisibleSetData( v ) + + m = IECore.MemoryIndexedIO( IECore.CharVectorData(), [], IECore.IndexedIO.OpenMode.Write ) + + d.save( m, "f" ) + + m2 = IECore.MemoryIndexedIO( m.buffer(), [], IECore.IndexedIO.OpenMode.Read ) + d2 = IECore.Object.load( m2, "f" ) + + self.assertEqual( d2, d ) + self.assertEqual( d2.hash(), d.hash() ) + self.assertEqual( d2.value, v ) + def testStoreInContext( self ) : v = GafferScene.VisibleSet() diff --git a/python/GafferSceneUI/HierarchyView.py b/python/GafferSceneUI/HierarchyView.py index 5de4b2071ea..db701a3e5ea 100644 --- a/python/GafferSceneUI/HierarchyView.py +++ b/python/GafferSceneUI/HierarchyView.py @@ -71,6 +71,9 @@ def __init__( self, scriptNode, **kw ) : with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + _VisibleSetBookmarkWidget() + GafferUI.Divider( GafferUI.Divider.Orientation.Vertical ) + _SearchFilterWidget( searchFilter ) _SetFilterWidget( setFilter ) @@ -356,3 +359,107 @@ def _updateFromPathFilter( self ) : def __patternEditingFinished( self, widget ) : self.pathFilter().setMatchPattern( self.__patternWidget.getText() ) + +########################################################################## +# _VisibleSetBookmarkWidget +########################################################################## + +class _VisibleSetBookmarkWidget( GafferUI.Widget ) : + + def __init__( self ) : + + button = GafferUI.MenuButton( + image = "bookmarks.png", + hasFrame = False, + toolTip = "Visible Set Bookmarks", + menu = GafferUI.Menu( Gaffer.WeakMethod( self.__menuDefinition ), title = "Visible Set Bookmarks" ) + ) + + GafferUI.Widget.__init__( self, button ) + + def __menuDefinition( self ) : + + scriptNode = self.ancestor( GafferUI.Editor ).scriptNode() + readOnly = Gaffer.MetadataAlgo.readOnly( scriptNode ) + + menuDefinition = IECore.MenuDefinition() + + bookmarks = sorted( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( scriptNode ) ) + if bookmarks : + for b in bookmarks : + menuDefinition.append( + "/" + b, + { + "command" : functools.partial( Gaffer.WeakMethod( self.__restore ), b ), + "checkBox" : GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( scriptNode, b ) == GafferSceneUI.ScriptNodeAlgo.getVisibleSet( scriptNode ), + } + ) + menuDefinition.append( "/RestoreBookmarkDivider", { "divider" : True } ) + + for b in bookmarks : + menuDefinition.append( + "/Save As/" + b, + { + "command" : functools.partial( Gaffer.WeakMethod( self.__saveAs ), b ), + "active" : not readOnly, + } + ) + menuDefinition.append( + "/Delete/" + b, + { + "command" : functools.partial( Gaffer.WeakMethod( self.__delete ), b ), + "active" : not readOnly, + } + ) + menuDefinition.append( "/Save As/Divider", { "divider" : True } ) + else : + menuDefinition.append( "/No Bookmarks Available", { "active" : False } ) + menuDefinition.append( "/NoBookmarksDivider", { "divider" : True } ) + + menuDefinition.append( + "/Save As/New Bookmark...", + { + "command" : functools.partial( Gaffer.WeakMethod( self.__save ) ), + "active" : not readOnly, + } + ) + + return menuDefinition + + def __save( self, *unused ) : + + d = GafferUI.TextInputDialogue( initialText = "", title = "Save Bookmark", confirmLabel = "Save" ) + name = d.waitForText( parentWindow = self.ancestor( GafferUI.Window ) ) + + if not name : + return + + if name in GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( self.ancestor( GafferUI.Editor ).scriptNode() ) : + c = GafferUI.ConfirmationDialogue( + "Replace existing bookmark?", + "A bookmark named {} already exists. Do you want to replace it?".format( name ), + confirmLabel = "Replace" + ) + if not c.waitForConfirmation( parentWindow = self.ancestor( GafferUI.Window ) ) : + return + + self.__saveAs( name ) + + def __saveAs( self, name, *unused ) : + + scriptNode = self.ancestor( GafferUI.Editor ).scriptNode() + visibleSet = GafferSceneUI.ScriptNodeAlgo.getVisibleSet( scriptNode ) + with Gaffer.UndoScope( scriptNode ) : + GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( scriptNode, name, visibleSet ) + + def __delete( self, name, *unused ) : + + scriptNode = self.ancestor( GafferUI.Editor ).scriptNode() + with Gaffer.UndoScope( scriptNode ) : + GafferSceneUI.ScriptNodeAlgo.removeVisibleSetBookmark( scriptNode, name ) + + def __restore( self, name, *unused ) : + + scriptNode = self.ancestor( GafferUI.Editor ).scriptNode() + visibleSet = GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( scriptNode, name ) + GafferSceneUI.ScriptNodeAlgo.setVisibleSet( scriptNode, visibleSet ) diff --git a/python/GafferSceneUITest/ScriptNodeAlgoTest.py b/python/GafferSceneUITest/ScriptNodeAlgoTest.py index 9cee235a184..2a6d02f7e68 100644 --- a/python/GafferSceneUITest/ScriptNodeAlgoTest.py +++ b/python/GafferSceneUITest/ScriptNodeAlgoTest.py @@ -261,5 +261,83 @@ def testGetCurrentRenderPass( self ) : script2["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", 123.0, "renderPass" ) self.assertRaises( IECore.Exception, GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass, script2 ) + def testVisibleSetBookmarks( self ) : + + s = Gaffer.ScriptNode() + self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [] ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ), + GafferScene.VisibleSet() + ) + + v = GafferScene.VisibleSet( + inclusions = IECore.PathMatcher( [ "/a" ] ), + exclusions = IECore.PathMatcher( [ "/b" ] ), + expansions = IECore.PathMatcher( [ "/c" ] ) + ) + + cs = GafferTest.CapturingSlot( Gaffer.Metadata.nodeValueChangedSignal( s ) ) + GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "test", v ) + self.assertTrue( len( cs ) ) + + self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test" ] ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ), + v + ) + + v2 = GafferScene.VisibleSet( v ) + v2.inclusions.addPath( "/d" ) + del cs[:] + GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "test2", v2 ) + self.assertTrue( len( cs ) ) + + self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test", "test2" ] ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test2" ), + v2 + ) + + del cs[:] + GafferSceneUI.ScriptNodeAlgo.removeVisibleSetBookmark( s, "test" ) + self.assertTrue( len( cs ) ) + + self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test2" ] ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ), + GafferScene.VisibleSet() + ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test2" ), + v2 + ) + + def testVisibleSetBookmarkSerialisation( self ) : + + s = Gaffer.ScriptNode() + + v = GafferScene.VisibleSet( + inclusions = IECore.PathMatcher( [ "/a" ] ), + exclusions = IECore.PathMatcher( [ "/b/b" ] ), + expansions = IECore.PathMatcher( [ "/c/c/c" ] ) + ) + GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "serialisationTest", v ) + self.assertIn( "serialisationTest", GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ) ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "serialisationTest" ), + v + ) + + se = s.serialise() + + s2 = Gaffer.ScriptNode() + s2.execute( se ) + + self.assertIn( "serialisationTest", GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s2 ) ) + self.assertEqual( + GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s2, "serialisationTest" ), + v + ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/MetadataTest.py b/python/GafferTest/MetadataTest.py index c13162a9f21..ec98346eff2 100644 --- a/python/GafferTest/MetadataTest.py +++ b/python/GafferTest/MetadataTest.py @@ -378,6 +378,43 @@ def testSerialisation( self ) : self.assertEqual( Gaffer.Metadata.value( s2["n"], "serialisationTest" ), 1 ) self.assertEqual( Gaffer.Metadata.value( s2["n"]["op1"], "serialisationTest" ), 2 ) + def testScriptNodeSerialisation( self ) : + + s = Gaffer.ScriptNode() + + Gaffer.Metadata.registerValue( s, "serialisationTest", 1 ) + Gaffer.Metadata.registerValue( s["variables"], "serialisationTest", 2 ) + + s2 = Gaffer.ScriptNode() + s2.execute( s.serialise() ) + + self.assertEqual( Gaffer.Metadata.value( s2, "serialisationTest" ), 1 ) + self.assertEqual( Gaffer.Metadata.value( s2["variables"], "serialisationTest" ), 2 ) + + def testScriptNodeMetadataDoesntLeak( self ) : + + a = Gaffer.ApplicationRoot() + s = Gaffer.ScriptNode() + a["scripts"].addChild( s ) + + s["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s, "scriptNodeSerialisationTest", 1 ) + Gaffer.Metadata.registerValue( s["n"], "serialisationTest", 1 ) + Gaffer.Metadata.registerValue( s["n"]["op1"], "serialisationTest", 2 ) + + s2 = Gaffer.ScriptNode() + a["scripts"].addChild( s2 ) + + s.selection().add( s["n"] ) + s.copy( parent = s, filter = s.selection() ) + + s2.paste() + + self.assertNotIn( "scriptNodeSerialisationTest", Gaffer.Metadata.registeredValues( s2 ) ) + self.assertEqual( Gaffer.Metadata.value( s2["n"], "serialisationTest" ), 1 ) + self.assertEqual( Gaffer.Metadata.value( s2["n"]["op1"], "serialisationTest" ), 2 ) + def testStringSerialisationWithNewlinesAndQuotes( self ) : trickyStrings = [ diff --git a/python/GafferUI/PythonEditor.py b/python/GafferUI/PythonEditor.py index ff410841f4c..9be0e84c6df 100644 --- a/python/GafferUI/PythonEditor.py +++ b/python/GafferUI/PythonEditor.py @@ -159,7 +159,7 @@ def __activated( self, widget ) : def __dropText( self, widget, dragData ) : - if isinstance( dragData, IECore.StringVectorData ) : + if IECore.DataTraits.isSequenceDataType( dragData ) : return repr( list( dragData ) ) elif isinstance( dragData, Gaffer.GraphComponent ) : if self.scriptNode().isAncestorOf( dragData ) : diff --git a/src/Gaffer/ScriptNode.cpp b/src/Gaffer/ScriptNode.cpp index fa085c662b2..633c2fcc50f 100644 --- a/src/Gaffer/ScriptNode.cpp +++ b/src/Gaffer/ScriptNode.cpp @@ -901,6 +901,14 @@ std::string ScriptNode::serialiseInternal( const Node *parent, const Set *filter { throw IECore::Exception( "Serialisation not available - please link to libGafferBindings." ); } + + Context::EditableScope scope( Context::current() ); + if( ( !parent || parent == this ) && !filter ) + { + static const bool includeParentMetadata = true; + scope.set( "serialiser:includeParentMetadata", &includeParentMetadata ); + } + return g_serialiseFunction( parent ? parent : this, filter ); } diff --git a/src/GafferML/TensorToMesh.cpp b/src/GafferML/TensorToMesh.cpp new file mode 100644 index 00000000000..fa259191bec --- /dev/null +++ b/src/GafferML/TensorToMesh.cpp @@ -0,0 +1,177 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2014, Lucien Fostier. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "GafferML/TensorToMesh.h" + +#include "IECoreScene/MeshPrimitive.h" + + +using namespace std; +using namespace Imath; +using namespace IECore; +using namespace IECoreScene; +using namespace Gaffer; +using namespace GafferML; + +GAFFER_NODE_DEFINE_TYPE( TensorToMesh ); + +size_t TensorToMesh::g_firstPlugIndex = 0; + +TensorToMesh::TensorToMesh( const std::string &name ) + : ObjectSource( name, "tensorMesh" ) +{ + + storeIndexOfNextChild( g_firstPlugIndex ); + + addChild( new TensorPlug( "position" ) ); + addChild( new TensorPlug( "vertexIds" ) ); + +} + +TensorToMesh::~TensorToMesh() +{ +} + +TensorPlug *TensorToMesh::positionTensorPlug() +{ + return getChild( g_firstPlugIndex ); +} + +const TensorPlug *TensorToMesh::positionTensorPlug() const +{ + return getChild( g_firstPlugIndex ); +} + +TensorPlug *TensorToMesh::vertexIdsTensorPlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const TensorPlug *TensorToMesh::vertexIdsTensorPlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + +void TensorToMesh::affects( const Plug *input, AffectedPlugsContainer &outputs ) const +{ + ObjectSource::affects( input, outputs ); + if ( input == positionTensorPlug() || input == vertexIdsTensorPlug() ) + { + outputs.push_back(sourcePlug()); + } + +} + +void TensorToMesh::hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + positionTensorPlug()->hash( h ); + vertexIdsTensorPlug()->hash( h ); +} + +IECore::ConstObjectPtr TensorToMesh::computeSource( const Context *context ) const +{ + ConstTensorPtr positionTensorData = positionTensorPlug()->getValue(); + + if( !positionTensorData->value() ) + { + throw IECore::Exception( "Empty Position tensor" ); + } + + auto positionShape = positionTensorData->shape(); + if ( positionShape.size() != 3 ) + { + throw IECore::Exception( "Invalid position tensor number of dimensions, should have 3 dimensions" ); + } + + if ( positionShape[2] % 3 != 0 ) + { + throw IECore::Exception( "Invalid position dimensions, only 3d coordinates are supported" ); + } + + if ( positionTensorData->value().GetTensorTypeAndShapeInfo().GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT ) + { + throw IECore::Exception( "Invalid data type input for position tensor, only float is currently supported" ); + } + + const size_t count = positionTensorData->value().GetTensorTypeAndShapeInfo().GetElementCount(); + + const float *sourceData = positionTensorData->value().GetTensorData(); + + ConstTensorPtr vertexIdsTensorData = vertexIdsTensorPlug()->getValue(); + if( !vertexIdsTensorData->value() ) + { + throw IECore::Exception( "Empty VertexIds tensor" ); + } + + if ( vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 ) + { + throw IECore::Exception( "Invalid data type input for vertexIds tensor, only int64 is currently supported" ); + } + + const int64_t *sourceVertexIdsData = vertexIdsTensorData->value().GetTensorData(); + + // Copy out topology + IntVectorDataPtr verticesPerFaceData = new IntVectorData; + vector &verticesPerFace = verticesPerFaceData->writable(); + + IntVectorDataPtr vertexIdsData = new IntVectorData; + vector &vertexIds = vertexIdsData->writable(); + + V3fVectorDataPtr pointsData = new V3fVectorData; + vector &points = pointsData->writable(); + + for( size_t i = 0; i < count; i += 3 ) + { + Imath::V3f v; + for( size_t j = 0; j < 3; j++ ) + { + v[j] = *( sourceData + ( i + j ) ); + } + points.push_back( v ); + } + + int vertexPerFace = vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetShape()[1]; + for( int i = 0; i < vertexIdsTensorData->value().GetTensorTypeAndShapeInfo().GetShape()[0]; i++ ) + { + verticesPerFace.push_back(vertexPerFace); + for ( int j = 0; j < vertexPerFace; j++ ) + { + vertexIds.push_back( *( sourceVertexIdsData + i * vertexPerFace + j ) ); + } + } + + return new MeshPrimitive( verticesPerFaceData, vertexIdsData, "linear", pointsData ); +} diff --git a/src/GafferMLModule/GafferMLModule.cpp b/src/GafferMLModule/GafferMLModule.cpp index dc0e32b16cf..031650535d2 100644 --- a/src/GafferMLModule/GafferMLModule.cpp +++ b/src/GafferMLModule/GafferMLModule.cpp @@ -46,6 +46,7 @@ #include "GafferML/Tensor.h" #include "GafferML/TensorPlug.h" #include "GafferML/TensorToImage.h" +#include "GafferML/TensorToMesh.h" #include "IECorePython/RunTimeTypedBinding.h" @@ -237,6 +238,7 @@ BOOST_PYTHON_MODULE( _GafferML ) GafferBindings::DependencyNodeClass() .def( "loadModel", &loadModelWrapper ) ; + GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); diff --git a/src/GafferScene/VisibleSetData.cpp b/src/GafferScene/VisibleSetData.cpp index 7a4ac5b903e..b8280030a58 100644 --- a/src/GafferScene/VisibleSetData.cpp +++ b/src/GafferScene/VisibleSetData.cpp @@ -36,10 +36,12 @@ #include "GafferScene/VisibleSetData.h" +#include "GafferScene/Export.h" #include "GafferScene/TypeIds.h" #include "Gaffer/Context.h" +#include "IECore/PathMatcherData.h" #include "IECore/TypedData.h" #include "IECore/TypedData.inl" @@ -47,6 +49,11 @@ namespace { Gaffer::Context::TypeDescription g_visibleSetDataTypeDescription; +const IndexedIO::EntryID g_inclusionsEntry( "inclusions" ); +const IndexedIO::EntryID g_exclusionsEntry( "exclusions" ); +const IndexedIO::EntryID g_expansionsEntry( "expansions" ); +const InternedString g_visibleSetEntry( "visibleSet" ); +const unsigned int g_ioVersion = 0; } // namespace @@ -55,16 +62,40 @@ namespace IECore IECORE_RUNTIMETYPED_DEFINETEMPLATESPECIALISATION( GafferScene::VisibleSetData, GafferScene::VisibleSetDataTypeId ) +/// \todo Remove once Cortex has been updated to provide these. template<> +void PathMatcherData::save( SaveContext *context ) const; + +template<> +void PathMatcherData::load( LoadContextPtr context ); + +template<> GAFFERSCENE_API void VisibleSetData::save( SaveContext *context ) const { - throw IECore::NotImplementedException( "VisibleSetData::save Not implemented" ); + Data::save( context ); + IndexedIOPtr container = context->container( staticTypeName(), g_ioVersion ); + IndexedIOPtr visibleSetIO = container->subdirectory( g_visibleSetEntry, IndexedIO::CreateIfMissing ); + + IECore::PathMatcherDataPtr inclusionsData = new IECore::PathMatcherData( readable().inclusions ); + IECore::PathMatcherDataPtr exclusionsData = new IECore::PathMatcherData( readable().exclusions ); + IECore::PathMatcherDataPtr expansionsData = new IECore::PathMatcherData( readable().expansions ); + + context->save( inclusionsData.get(), visibleSetIO.get(), g_inclusionsEntry ); + context->save( exclusionsData.get(), visibleSetIO.get(), g_exclusionsEntry ); + context->save( expansionsData.get(), visibleSetIO.get(), g_expansionsEntry ); } -template<> +template<> GAFFERSCENE_API void VisibleSetData::load( LoadContextPtr context ) { - throw IECore::NotImplementedException( "VisibleSetData::load Not implemented" ); + Data::load( context ); + unsigned int v = 0; + ConstIndexedIOPtr container = context->container( staticTypeName(), v ); + ConstIndexedIOPtr visibleSetIO = container->subdirectory( g_visibleSetEntry, IndexedIO::NullIfMissing ); + + writable().inclusions = context->load( visibleSetIO.get(), g_inclusionsEntry )->readable(); + writable().exclusions = context->load( visibleSetIO.get(), g_exclusionsEntry )->readable(); + writable().expansions = context->load( visibleSetIO.get(), g_expansionsEntry )->readable(); } template class TypedData; diff --git a/src/GafferSceneUI/ScriptNodeAlgo.cpp b/src/GafferSceneUI/ScriptNodeAlgo.cpp index af6a22a62f6..99acd62ea8e 100644 --- a/src/GafferSceneUI/ScriptNodeAlgo.cpp +++ b/src/GafferSceneUI/ScriptNodeAlgo.cpp @@ -38,12 +38,16 @@ #include "GafferSceneUI/ContextAlgo.h" +#include "GafferScene/VisibleSetData.h" + #include "Gaffer/Context.h" +#include "Gaffer/Metadata.h" #include "Gaffer/ScriptNode.h" #include "Gaffer/NameValuePlug.h" #include "Gaffer/CompoundDataPlug.h" #include "Gaffer/MetadataAlgo.h" +#include "boost/algorithm/string/predicate.hpp" #include "boost/bind/bind.hpp" #include @@ -55,6 +59,8 @@ using namespace GafferSceneUI; namespace { +const std::string g_visibleSetBookmarkPrefix( "visibleSet:bookmark:" ); + struct ChangedSignals { ScriptNodeAlgo::ChangedSignal visibleSetChangedSignal; @@ -192,3 +198,43 @@ std::string ScriptNodeAlgo::getCurrentRenderPass( const Gaffer::ScriptNode *scri return ""; } + +// Visible Set Bookmarks +// ===================== + +void ScriptNodeAlgo::addVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent ) +{ + Metadata::registerValue( script, g_visibleSetBookmarkPrefix + name, new GafferScene::VisibleSetData( visibleSet ), persistent ); +} + +GafferScene::VisibleSet ScriptNodeAlgo::getVisibleSetBookmark( const Gaffer::ScriptNode *script, const std::string &name ) +{ + if( const auto bookmarkData = Metadata::value( script, g_visibleSetBookmarkPrefix + name ) ) + { + return bookmarkData->readable(); + } + + return GafferScene::VisibleSet(); +} + +void ScriptNodeAlgo::removeVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name ) +{ + Metadata::deregisterValue( script, g_visibleSetBookmarkPrefix + name ); +} + +std::vector ScriptNodeAlgo::visibleSetBookmarks( const Gaffer::ScriptNode *script ) +{ + std::vector keys; + Metadata::registeredValues( script, keys ); + + std::vector result; + for( const auto &key : keys ) + { + if( boost::starts_with( key.string(), g_visibleSetBookmarkPrefix ) ) + { + result.push_back( key.string().substr( g_visibleSetBookmarkPrefix.size(), key.string().size() ) ); + } + } + + return result; +} diff --git a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp index ed3374d1ab1..50b6b600a58 100644 --- a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp +++ b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp @@ -115,6 +115,28 @@ std::string getCurrentRenderPassWrapper( ScriptNode &script ) return getCurrentRenderPass( &script ); } +void addVisibleSetBookmarkWrapper( ScriptNode &script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent ) +{ + IECorePython::ScopedGILRelease gilRelease; + addVisibleSetBookmark( &script, name, visibleSet, persistent ); +} + +void removeVisibleSetBookmarkWrapper( ScriptNode &script, const std::string &name ) +{ + IECorePython::ScopedGILRelease gilRelease; + removeVisibleSetBookmark( &script, name ); +} + +list visibleSetBookmarksWrapper( const ScriptNode &script ) +{ + list result; + for( const auto &n : visibleSetBookmarks( &script ) ) + { + result.append( n ); + } + return result; +} + } // namespace void GafferSceneUIModule::bindScriptNodeAlgo() @@ -137,4 +159,9 @@ void GafferSceneUIModule::bindScriptNodeAlgo() def( "acquireRenderPassPlug", &acquireRenderPassPlugWrapper, ( arg( "script" ), arg( "createIfMissing" ) = true ) ); def( "setCurrentRenderPass", &setCurrentRenderPassWrapper ); def( "getCurrentRenderPass", &getCurrentRenderPassWrapper ); + + def( "addVisibleSetBookmark", &addVisibleSetBookmarkWrapper, ( arg( "script" ), arg( "name" ), arg( "visibleSet" ), arg( "persistent" ) = true ) ); + def( "getVisibleSetBookmark", getVisibleSetBookmark, ( arg( "script" ), arg( "name" ) ) ); + def( "removeVisibleSetBookmark", &removeVisibleSetBookmarkWrapper, ( arg( "script" ), arg( "name" ) ) ); + def( "visibleSetBookmarks", &visibleSetBookmarksWrapper, ( arg( "script" ) ) ); } diff --git a/startup/gui/menus.py b/startup/gui/menus.py index 6f72e491ac2..184dd38501e 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -567,6 +567,7 @@ def __usdLightCreator( lightType ) : nodeMenu.append( "/ML/Image To Tensor", GafferML.ImageToTensor, searchText = "ImageToTensor" ) nodeMenu.append( "/ML/Tensor To Image", GafferML.TensorToImage, searchText = "TensorToImage" ) nodeMenu.append( "/ML/Inference", GafferML.Inference, searchText = "Inference" ) + nodeMenu.append( "/ML/Tensor To Mesh", GafferML.TensorToMesh, searchText = "TensorToMesh" ) # Utility nodes