From d7427ab2720e5786e8837b10dc572de647875197 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:02:42 -0700 Subject: [PATCH 1/9] Path : Add `inspectionContext` --- Changes.md | 1 + include/Gaffer/Path.h | 5 +++++ src/Gaffer/Path.cpp | 5 +++++ src/GafferModule/PathBinding.cpp | 22 ++++++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/Changes.md b/Changes.md index 8a57bb8b7ec..ed3783a9418 100644 --- a/Changes.md +++ b/Changes.md @@ -38,6 +38,7 @@ API - A `DeprecationWarning` is now emitted for any subclasses still implementing the legacy `_updateFromPlug()` or `_updateFromPlugs()` methods. Implement `_updateFromValues()`, `_updateFromMetadata()` and `_updateFromEditable()` instead. - A `DeprecationWarning` is now emitted by `_plugConnections()`. Use `_blockedUpdateFromValues()` instead. - NodeGadget, ConnectionGadget : Added `updateFromContextTracker()` virtual methods. +- Path : Added `inspectionContext()` virtual method. Breaking Changes ---------------- diff --git a/include/Gaffer/Path.h b/include/Gaffer/Path.h index 71a7c07ed9f..df70019ab49 100644 --- a/include/Gaffer/Path.h +++ b/include/Gaffer/Path.h @@ -37,6 +37,7 @@ #pragma once +#include "Gaffer/Context.h" #include "Gaffer/Export.h" #include "Gaffer/Signals.h" #include "Gaffer/TypeIds.h" @@ -190,6 +191,10 @@ class GAFFER_API Path : public IECore::RunTimeTyped /// made. virtual const Plug *cancellationSubject() const; + /// May be implemented by Paths to provide a Context useful for inspecting + /// data represented by the Path. + virtual ContextPtr inspectionContext( const IECore::Canceller *canceller = nullptr ) const; + protected : /// The subclass specific part of children(). This must be implemented diff --git a/src/Gaffer/Path.cpp b/src/Gaffer/Path.cpp index 3ebf3506c86..794903cb14a 100644 --- a/src/Gaffer/Path.cpp +++ b/src/Gaffer/Path.cpp @@ -379,6 +379,11 @@ const Plug *Path::cancellationSubject() const return nullptr; } +ContextPtr Path::inspectionContext( const IECore::Canceller *canceller ) const +{ + return nullptr; +} + void Path::doChildren( std::vector &children, const IECore::Canceller *canceller ) const { } diff --git a/src/GafferModule/PathBinding.cpp b/src/GafferModule/PathBinding.cpp index 1557e2fc670..a78e9929522 100644 --- a/src/GafferModule/PathBinding.cpp +++ b/src/GafferModule/PathBinding.cpp @@ -269,6 +269,27 @@ class PathWrapper : public IECorePython::RunTimeTypedWrapper return WrappedType::cancellationSubject(); } + ContextPtr inspectionContext( const IECore::Canceller *canceller = nullptr ) const override + { + if( this->isSubclassed() ) + { + IECorePython::ScopedGILLock gilLock; + try + { + boost::python::object f = this->methodOverride( "inspectionContext" ); + if( f ) + { + return extract( f( boost::python::ptr( canceller ) ) ); + } + } + catch( const error_already_set & ) + { + ExceptionAlgo::translatePythonException(); + } + } + return WrappedType::inspectionContext(); + } + void doChildren( std::vector &children, const IECore::Canceller *canceller = nullptr ) const override { if( this->isSubclassed() ) @@ -554,6 +575,7 @@ void GafferModule::bindPath() .def( "setFromString", &Path::setFromString, return_self<>() ) .def( "append", &Path::append, return_self<>() ) .def( "truncateUntilValid", &Path::truncateUntilValid, return_self<>() ) + .def( "inspectionContext", &Path::inspectionContext, ( arg_( "path" ), arg_( "canceller" ) = object() ) ) .def( "__str__", &Path::string ) .def( "__repr__", &pathRepr ) .def( "__len__", &pathLength ) From 7b9f51c91f17821665bc3193535a8f1619ae6221 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:07:46 -0700 Subject: [PATCH 2/9] RenderPassEditor : Implement RenderPassPath::inspectionContext In this case we set `renderPass` to the name of the render pass represented by the path, or return `nullptr` if the path does not represent a render pass. --- .../GafferSceneUITest/RenderPassEditorTest.py | 33 +++++++++++++++++++ .../RenderPassEditorBinding.cpp | 18 ++++++++++ 2 files changed, 51 insertions(+) diff --git a/python/GafferSceneUITest/RenderPassEditorTest.py b/python/GafferSceneUITest/RenderPassEditorTest.py index 06538583077..430b2956bf6 100644 --- a/python/GafferSceneUITest/RenderPassEditorTest.py +++ b/python/GafferSceneUITest/RenderPassEditorTest.py @@ -134,6 +134,39 @@ def testRenderPassPathCancellation( self ) : with self.assertRaises( IECore.Cancelled ) : path.property( "renderPassPath:enabled", canceller ) + def testRenderPassPathInspectionContext( self ) : + + def testFn( name ) : + return "/".join( name.split( "_" )[:-1] ) + + GafferSceneUI.RenderPassEditor.registerPathGroupingFunction( testFn ) + + renderPasses = GafferScene.RenderPasses() + renderPasses["names"].setValue( IECore.StringVectorData( ["A", "A_A", "B"] ) ) + + for path, renderPass, grouped in ( + ( "/A_A", "A_A", False ), + ( "/A_A", None, True ), + ( "/A", "A", False ), + ( "/A", "A", True ), + ( "/A/A_A", None, False ), + ( "/A/A_A", "A_A", True ), + ( "/B", "B", False ), + ( "/B", "B", True ), + ( "/BOGUS", None, False ), + ( "/BOGUS", None, True ), + ( "/B/OGUS", None, False ), + ( "/B/OGUS", None, True ), + ) : + + path = _GafferSceneUI._RenderPassEditor.RenderPassPath( renderPasses["out"], Gaffer.Context(), path, grouped = grouped ) + inspectionContext = path.inspectionContext() + if renderPass is not None : + self.assertIn( "renderPass", inspectionContext ) + self.assertEqual( inspectionContext["renderPass"], renderPass ) + else : + self.assertIsNone( inspectionContext ) + def testSearchFilter( self ) : renderPasses = GafferScene.RenderPasses() diff --git a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp index 1f4c096d215..067b2343a1a 100644 --- a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp +++ b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp @@ -346,6 +346,24 @@ class RenderPassPath : public Gaffer::Path return m_scene.get(); } + Gaffer::ContextPtr inspectionContext( const IECore::Canceller *canceller ) const override + { + const auto renderPassName = runTimeCast( property( g_renderPassNamePropertyName, canceller ) ); + if( !renderPassName ) + { + return nullptr; + } + + Context::EditableScope scope( getContext() ); + scope.set( g_renderPassContextName, &( renderPassName->readable() ) ); + if( canceller ) + { + scope.setCanceller( canceller ); + } + + return new Context( *scope.context() ); + } + protected : void doChildren( std::vector &children, const IECore::Canceller *canceller ) const override From 37d843438d7fcf1a9ba1f8949bf9d4113777b1e4 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:08:13 -0700 Subject: [PATCH 3/9] ScenePath : Implement inspectionContext This returns a context containing `scene:path` representing the path. --- include/GafferScene/ScenePath.h | 2 ++ python/GafferSceneTest/ScenePathTest.py | 21 +++++++++++++++++++++ src/GafferScene/ScenePath.cpp | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/include/GafferScene/ScenePath.h b/include/GafferScene/ScenePath.h index f205c2659a5..689a4a326cd 100644 --- a/include/GafferScene/ScenePath.h +++ b/include/GafferScene/ScenePath.h @@ -83,6 +83,8 @@ class GAFFERSCENE_API ScenePath : public Gaffer::Path const Gaffer::Plug *cancellationSubject() const override; + Gaffer::ContextPtr inspectionContext( const IECore::Canceller *canceller = nullptr ) const override; + static Gaffer::PathFilterPtr createStandardFilter( const std::vector &setNames = std::vector(), const std::string &setsLabel = "" ); protected : diff --git a/python/GafferSceneTest/ScenePathTest.py b/python/GafferSceneTest/ScenePathTest.py index 0e50aff965d..546da4c9b2d 100644 --- a/python/GafferSceneTest/ScenePathTest.py +++ b/python/GafferSceneTest/ScenePathTest.py @@ -292,5 +292,26 @@ def testCancellation( self ) : with self.assertRaises( IECore.Cancelled ) : path.isValid( canceller ) + def testInspectionContext( self ) : + + plane = GafferScene.Plane() + path = GafferScene.ScenePath( plane["out"], Gaffer.Context(), "/plane" ) + + inspectionContext = path.inspectionContext() + self.assertIsNotNone( inspectionContext ) + self.assertIn( "scene:path", inspectionContext ) + self.assertEqual( inspectionContext["scene:path"], GafferScene.ScenePlug.stringToPath( "/plane" ) ) + + context = Gaffer.Context() + context["foo"] = 123 + path = GafferScene.ScenePath( plane["out"], context, "/plane/bogus" ) + + inspectionContext = path.inspectionContext() + self.assertIsNotNone( inspectionContext ) + self.assertIn( "scene:path", inspectionContext ) + self.assertEqual( inspectionContext["scene:path"], GafferScene.ScenePlug.stringToPath( "/plane/bogus" ) ) + self.assertIn( "foo", inspectionContext ) + self.assertEqual( inspectionContext["foo"], 123 ) + if __name__ == "__main__": unittest.main() diff --git a/src/GafferScene/ScenePath.cpp b/src/GafferScene/ScenePath.cpp index 088ee36a23a..ebbf5c146e1 100644 --- a/src/GafferScene/ScenePath.cpp +++ b/src/GafferScene/ScenePath.cpp @@ -134,6 +134,17 @@ const Gaffer::Context *ScenePath::getContext() const return m_context.get(); } +Gaffer::ContextPtr ScenePath::inspectionContext( const IECore::Canceller *canceller ) const +{ + ScenePlug::PathScope scope( getContext(), &( names() ) ); + if( canceller ) + { + scope.setCanceller( canceller ); + } + + return new Context( *scope.context() ); +} + bool ScenePath::isValid( const IECore::Canceller *canceller ) const { if( !Path::isValid() ) From fe943984c4188713befc48e66949fd2211bf6a20 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:09:58 -0700 Subject: [PATCH 4/9] GafferSceneUI : Add InspectorColumn Both the LightEditor and RenderPassEditor have evolved their own versions of this, so we now consolidate into a common InspectorColumn usable by any editor that wishes to add a column using an Inspector to their PathListingWidget. Later commits will migrate both the LightEditor and RenderPassEditor to use this instead of their internal implementations. --- .../GafferSceneUI/Private/InspectorColumn.h | 86 ++++++++++ .../GafferSceneUITest/InspectorColumnTest.py | 81 +++++++++ python/GafferSceneUITest/__init__.py | 1 + src/GafferSceneUI/InspectorColumn.cpp | 162 ++++++++++++++++++ .../GafferSceneUIModule.cpp | 2 + .../InspectorColumnBinding.cpp | 79 +++++++++ .../InspectorColumnBinding.h | 44 +++++ 7 files changed, 455 insertions(+) create mode 100644 include/GafferSceneUI/Private/InspectorColumn.h create mode 100644 python/GafferSceneUITest/InspectorColumnTest.py create mode 100644 src/GafferSceneUI/InspectorColumn.cpp create mode 100644 src/GafferSceneUIModule/InspectorColumnBinding.cpp create mode 100644 src/GafferSceneUIModule/InspectorColumnBinding.h diff --git a/include/GafferSceneUI/Private/InspectorColumn.h b/include/GafferSceneUI/Private/InspectorColumn.h new file mode 100644 index 00000000000..c78fc6f4bec --- /dev/null +++ b/include/GafferSceneUI/Private/InspectorColumn.h @@ -0,0 +1,86 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferSceneUI/Export.h" +#include "GafferSceneUI/Private/Inspector.h" + +#include "GafferUI/PathColumn.h" + +#include "Gaffer/Path.h" + +using namespace IECore; + +namespace GafferSceneUI +{ + +namespace Private +{ + +/// Column type which makes use of an Inspector. +class GAFFERSCENEUI_API InspectorColumn : public GafferUI::PathColumn +{ + + public : + + IE_CORE_DECLAREMEMBERPTR( InspectorColumn ) + + InspectorColumn( GafferSceneUI::Private::InspectorPtr inspector, const std::string &label, const std::string &toolTip = "", PathColumn::SizeMode sizeMode = Default ); + InspectorColumn( GafferSceneUI::Private::InspectorPtr inspector, const CellData &headerData, PathColumn::SizeMode sizeMode = Default ); + + /// Returns the inspector used by this column. + GafferSceneUI::Private::Inspector *inspector() const; + + CellData cellData( const Gaffer::Path &path, const IECore::Canceller *canceller ) const override; + CellData headerData( const IECore::Canceller *canceller ) const override; + + private : + + void inspectorDirtied(); + + static IECore::ConstStringDataPtr headerValue( const std::string &columnName ); + + const Private::InspectorPtr m_inspector; + const CellData m_headerData; + +}; + +IE_CORE_DECLAREPTR( InspectorColumn ) + +} // namespace Private + +} // namespace GafferSceneUI diff --git a/python/GafferSceneUITest/InspectorColumnTest.py b/python/GafferSceneUITest/InspectorColumnTest.py new file mode 100644 index 00000000000..9099ce7f1a6 --- /dev/null +++ b/python/GafferSceneUITest/InspectorColumnTest.py @@ -0,0 +1,81 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Limited. 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 GafferUI +import GafferUITest +import GafferSceneTest +import GafferSceneUI + +class InspectorColumnTest( GafferUITest.TestCase ) : + + def testInspectorColumnConstructors( self ) : + + light = GafferSceneTest.TestLight() + + inspector = GafferSceneUI.Private.AttributeInspector( light["out"], None, "gl:visualiser:scale" ) + + c = GafferSceneUI.Private.InspectorColumn( inspector, "label", "help!" ) + self.assertEqual( c.inspector(), inspector ) + self.assertEqual( c.getSizeMode(), GafferUI.PathColumn.SizeMode.Default ) + self.assertEqual( c.headerData().value, "Label" ) + self.assertEqual( c.headerData().toolTip, "help!" ) + + c = GafferSceneUI.Private.InspectorColumn( inspector, "Fancy ( Label )", "" ) + self.assertEqual( c.inspector(), inspector ) + self.assertEqual( c.getSizeMode(), GafferUI.PathColumn.SizeMode.Default ) + self.assertEqual( c.headerData().value, "Fancy ( Label )" ) + self.assertEqual( c.headerData().toolTip, "" ) + + c = GafferSceneUI.Private.InspectorColumn( inspector ) + self.assertEqual( c.inspector(), inspector ) + self.assertEqual( c.getSizeMode(), GafferUI.PathColumn.SizeMode.Default ) + self.assertEqual( c.headerData().value, "Gl:visualiser:scale" ) + self.assertEqual( c.headerData().toolTip, "" ) + + c = GafferSceneUI.Private.InspectorColumn( + inspector, + GafferUI.PathColumn.CellData( value = "Fancy ( Label )", toolTip = "help!" ), + GafferUI.PathColumn.SizeMode.Stretch + ) + self.assertEqual( c.inspector(), inspector ) + self.assertEqual( c.getSizeMode(), GafferUI.PathColumn.SizeMode.Stretch ) + self.assertEqual( c.headerData().value, "Fancy ( Label )" ) + self.assertEqual( c.headerData().toolTip, "help!" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneUITest/__init__.py b/python/GafferSceneUITest/__init__.py index 157aeaa6fe0..6f2758c08c9 100644 --- a/python/GafferSceneUITest/__init__.py +++ b/python/GafferSceneUITest/__init__.py @@ -63,6 +63,7 @@ from .LightPositionToolTest import LightPositionToolTest from .RenderPassEditorTest import RenderPassEditorTest from .SelectionToolTest import SelectionToolTest +from .InspectorColumnTest import InspectorColumnTest if __name__ == "__main__": unittest.main() diff --git a/src/GafferSceneUI/InspectorColumn.cpp b/src/GafferSceneUI/InspectorColumn.cpp new file mode 100644 index 00000000000..57d85de263b --- /dev/null +++ b/src/GafferSceneUI/InspectorColumn.cpp @@ -0,0 +1,162 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "GafferSceneUI/Private/InspectorColumn.h" + +#include "GafferUI/PathColumn.h" + +#include "Gaffer/ScriptNode.h" + +#include "IECore/CamelCase.h" +#include "IECore/SimpleTypedData.h" + +using namespace Gaffer; +using namespace GafferUI; +using namespace GafferScene; +using namespace GafferSceneUI::Private; + +namespace +{ + +const boost::container::flat_map g_sourceTypeColors = { +{ (int)Inspector::Result::SourceType::Upstream, nullptr }, +{ (int)Inspector::Result::SourceType::EditScope, new Color4fData( Imath::Color4f( 48, 100, 153, 150 ) / 255.0f ) }, +{ (int)Inspector::Result::SourceType::Downstream, new Color4fData( Imath::Color4f( 239, 198, 24, 104 ) / 255.0f ) }, +{ (int)Inspector::Result::SourceType::Other, nullptr }, +{ (int)Inspector::Result::SourceType::Fallback, nullptr }, +}; +const Color4fDataPtr g_fallbackValueForegroundColor = new Color4fData( Imath::Color4f( 163, 163, 163, 255 ) / 255.0f ); + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// InspectorColumn +////////////////////////////////////////////////////////////////////////// + +InspectorColumn::InspectorColumn( GafferSceneUI::Private::InspectorPtr inspector, const std::string &columnName, const std::string &columnToolTip, PathColumn::SizeMode sizeMode ) + : InspectorColumn( inspector, PathColumn::CellData( headerValue( columnName != "" ? columnName : inspector->name() ), nullptr, nullptr, new IECore::StringData( columnToolTip ) ), sizeMode ) +{ +} + +InspectorColumn::InspectorColumn( GafferSceneUI::Private::InspectorPtr inspector, const CellData &headerData, PathColumn::SizeMode sizeMode ) + : PathColumn( sizeMode ), m_inspector( inspector ), m_headerData( headerData ) +{ + m_inspector->dirtiedSignal().connect( boost::bind( &InspectorColumn::inspectorDirtied, this ) ); +} + +GafferSceneUI::Private::Inspector *InspectorColumn::inspector() const +{ + return m_inspector.get(); +} + +PathColumn::CellData InspectorColumn::cellData( const Gaffer::Path &path, const IECore::Canceller *canceller ) const +{ + CellData result; + + const ContextPtr inspectionContext = path.inspectionContext( canceller ); + if( !inspectionContext ) + { + return result; + } + + Context::Scope scope( inspectionContext.get() ); + Inspector::ConstResultPtr inspectorResult = m_inspector->inspect(); + if( !inspectorResult ) + { + return result; + } + + result.value = runTimeCast( inspectorResult->value() ); + /// \todo Should PathModel create a decoration automatically when we + /// return a colour for `Role::Value`? + result.icon = runTimeCast( inspectorResult->value() ); + result.background = g_sourceTypeColors.at( (int)inspectorResult->sourceType() ); + std::string toolTip; + if( inspectorResult->sourceType() == Inspector::Result::SourceType::Fallback ) + { + toolTip = "Source : " + inspectorResult->fallbackDescription(); + result.foreground = g_fallbackValueForegroundColor; + } + else if( const auto source = inspectorResult->source() ) + { + toolTip = "Source : " + source->relativeName( source->ancestor() ); + } + + /// \todo Adding these "Double-click" prompts really only makes sense + /// once the column itself handles editing. Should we have the ability + /// to create read-only columns? + if( inspectorResult->editable() ) + { + toolTip += !toolTip.empty() ? "\n\n" : ""; + if( runTimeCast( result.value ) ) + { + toolTip += "Double-click to toggle"; + } + else + { + toolTip += "Double-click to edit"; + } + } + + if( !toolTip.empty() ) + { + result.toolTip = new StringData( toolTip ); + } + + return result; +} + +PathColumn::CellData InspectorColumn::headerData( const IECore::Canceller *canceller ) const +{ + return m_headerData; +} + +void InspectorColumn::inspectorDirtied() +{ + changedSignal()( this ); +} + +IECore::ConstStringDataPtr InspectorColumn::headerValue( const std::string &columnName ) +{ + std::string name = columnName; + // Convert from snake case and/or camel case to UI case. + if( name.find( '_' ) != std::string::npos ) + { + std::replace( name.begin(), name.end(), '_', ' ' ); + name = CamelCase::fromSpaced( name ); + } + return new StringData( CamelCase::toSpaced( name ) ); +} diff --git a/src/GafferSceneUIModule/GafferSceneUIModule.cpp b/src/GafferSceneUIModule/GafferSceneUIModule.cpp index 77c6ad2f538..bd49b1593b8 100644 --- a/src/GafferSceneUIModule/GafferSceneUIModule.cpp +++ b/src/GafferSceneUIModule/GafferSceneUIModule.cpp @@ -47,6 +47,7 @@ #include "QueryBinding.h" #include "SetEditorBinding.h" #include "RenderPassEditorBinding.h" +#include "InspectorColumnBinding.h" using namespace GafferSceneUIModule; @@ -61,6 +62,7 @@ BOOST_PYTHON_MODULE( _GafferSceneUI ) bindContextAlgo(); bindQueries(); bindInspector(); + bindInspectorColumn(); bindLightEditor(); bindSetEditor(); bindRenderPassEditor(); diff --git a/src/GafferSceneUIModule/InspectorColumnBinding.cpp b/src/GafferSceneUIModule/InspectorColumnBinding.cpp new file mode 100644 index 00000000000..2f90dc87c4d --- /dev/null +++ b/src/GafferSceneUIModule/InspectorColumnBinding.cpp @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "InspectorColumnBinding.h" + +#include "GafferSceneUI/Private/Inspector.h" +#include "GafferSceneUI/Private/InspectorColumn.h" + +#include "GafferUI/PathColumn.h" + +#include "IECorePython/RefCountedBinding.h" + +using namespace boost::python; +using namespace IECorePython; +using namespace GafferUI; +using namespace GafferSceneUI::Private; + +void GafferSceneUIModule::bindInspectorColumn() +{ + + object privateModule( borrowed( PyImport_AddModule( "GafferSceneUI.Private" ) ) ); + scope().attr( "Private" ) = privateModule; + scope privateScope( privateModule ); + + RefCountedClass( "InspectorColumn" ) + .def( init( + ( + arg_( "inspector" ), + arg_( "label" ) = "", + arg_( "toolTip" ) = "", + arg( "sizeMode" ) = PathColumn::Default + ) + ) ) + .def( init( + ( + arg_( "inspector" ), + arg_( "headerData" ), + arg_( "sizeMode" ) = PathColumn::Default + ) + ) ) + .def( "inspector", &InspectorColumn::inspector, return_value_policy() ) + ; + +} diff --git a/src/GafferSceneUIModule/InspectorColumnBinding.h b/src/GafferSceneUIModule/InspectorColumnBinding.h new file mode 100644 index 00000000000..d887ec63043 --- /dev/null +++ b/src/GafferSceneUIModule/InspectorColumnBinding.h @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +namespace GafferSceneUIModule +{ + +void bindInspectorColumn(); + +} // namespace GafferSceneUIModule From e2cdb2a6159f3e68286d52a4d859387f5ceb6000 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:10:11 -0700 Subject: [PATCH 5/9] InspectorColumn : Improve formatting of column header Otherwise we currently see artifacts with column names not specified in snake and/or camel case, such as additional spaces being inserted before upper case characters with leading whitespace by `CamelCase::toSpaced()`. --- python/GafferSceneUITest/InspectorColumnTest.py | 2 +- src/GafferSceneUI/InspectorColumn.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/python/GafferSceneUITest/InspectorColumnTest.py b/python/GafferSceneUITest/InspectorColumnTest.py index 9099ce7f1a6..fd93f6c338c 100644 --- a/python/GafferSceneUITest/InspectorColumnTest.py +++ b/python/GafferSceneUITest/InspectorColumnTest.py @@ -58,7 +58,7 @@ def testInspectorColumnConstructors( self ) : c = GafferSceneUI.Private.InspectorColumn( inspector, "Fancy ( Label )", "" ) self.assertEqual( c.inspector(), inspector ) self.assertEqual( c.getSizeMode(), GafferUI.PathColumn.SizeMode.Default ) - self.assertEqual( c.headerData().value, "Fancy ( Label )" ) + self.assertEqual( c.headerData().value, "Fancy ( Label )" ) self.assertEqual( c.headerData().toolTip, "" ) c = GafferSceneUI.Private.InspectorColumn( inspector ) diff --git a/src/GafferSceneUI/InspectorColumn.cpp b/src/GafferSceneUI/InspectorColumn.cpp index 57d85de263b..173f04ef9ed 100644 --- a/src/GafferSceneUI/InspectorColumn.cpp +++ b/src/GafferSceneUI/InspectorColumn.cpp @@ -151,6 +151,13 @@ void InspectorColumn::inspectorDirtied() IECore::ConstStringDataPtr InspectorColumn::headerValue( const std::string &columnName ) { + if( columnName.find( ' ' ) != std::string::npos ) + { + // Names already containing spaces are considered + // to be already formatted and are left as-is. + return new StringData( columnName ); + } + std::string name = columnName; // Convert from snake case and/or camel case to UI case. if( name.find( '_' ) != std::string::npos ) From fc013da48df5d7b9226c4f2d55b25dbc8e68e3d2 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:13:03 -0700 Subject: [PATCH 6/9] RenderPassEditor : Use InspectorColumn Remove OptionInspectorColumn and replace it with GafferSceneUI::Private::InspectorColumn. --- python/GafferSceneUI/RenderPassEditor.py | 12 +- .../RenderPassEditorBinding.cpp | 153 ------------------ 2 files changed, 6 insertions(+), 159 deletions(-) diff --git a/python/GafferSceneUI/RenderPassEditor.py b/python/GafferSceneUI/RenderPassEditor.py index e281a7c1023..e632e1a5617 100644 --- a/python/GafferSceneUI/RenderPassEditor.py +++ b/python/GafferSceneUI/RenderPassEditor.py @@ -161,7 +161,7 @@ def registerOption( cls, groupKey, optionName, section = "Main", columnName = No GafferSceneUI.RenderPassEditor.registerColumn( groupKey, optionName, - lambda scene, editScope : _GafferSceneUI._RenderPassEditor.OptionInspectorColumn( + lambda scene, editScope : GafferSceneUI.Private.InspectorColumn( GafferSceneUI.Private.OptionInspector( scene, editScope, optionName ), columnName, toolTip @@ -172,7 +172,7 @@ def registerOption( cls, groupKey, optionName, section = "Main", columnName = No # Registers a column in the Render Pass Editor. # `inspectorFunction` is a callable object of the form # `inspectorFunction( scene, editScope )` returning a - # `GafferSceneUI._RenderPassEditor.OptionInspectorColumn` object. + # `GafferSceneUI.Private.InspectorColumn` object. @classmethod def registerColumn( cls, groupKey, columnKey, inspectorFunction, section = "Main" ) : @@ -398,7 +398,7 @@ def __editSelectedCells( self, pathListing, quickBoolean = True ) : with Gaffer.Context( self.context() ) as context : renderPassPath = self.__pathListing.getPath().copy() for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, _GafferSceneUI._RenderPassEditor.OptionInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for path in selection.paths() : renderPassPath.setFromString( path ) @@ -499,7 +499,7 @@ def __disablableInspectionTweaks( self, pathListing ) : with Gaffer.Context( self.context() ) as context : renderPassPath = self.__pathListing.getPath().copy() for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, _GafferSceneUI._RenderPassEditor.OptionInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for path in columnSelection.paths() : renderPassPath.setFromString( path ) @@ -553,7 +553,7 @@ def __selectedSetExpressions( self, pathListing ) : for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : if ( not columnSelection.isEmpty() and ( - not isinstance( column, _GafferSceneUI._RenderPassEditor.OptionInspectorColumn ) or + not isinstance( column, GafferSceneUI.Private.InspectorColumn ) or not ( Gaffer.Metadata.value( "option:" + column.inspector().name(), "ui:scene:acceptsSetName" ) or Gaffer.Metadata.value( "option:" + column.inspector().name(), "ui:scene:acceptsSetNames" ) or @@ -675,7 +675,7 @@ def __showEditHistory( self, *unused ) : for i in range( 0, len( columns ) ) : column = columns[ i ] - if not isinstance( column, _GafferSceneUI._RenderPassEditor.OptionInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for path in selection[i].paths() : diff --git a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp index 067b2343a1a..97b34dca1de 100644 --- a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp +++ b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp @@ -38,9 +38,6 @@ #include "RenderPassEditorBinding.h" -#include "GafferSceneUI/Private/Inspector.h" -#include "GafferSceneUI/Private/OptionInspector.h" - #include "GafferSceneUI/ContextAlgo.h" #include "GafferSceneUI/TypeIds.h" @@ -54,12 +51,10 @@ #include "Gaffer/Node.h" #include "Gaffer/Path.h" #include "Gaffer/PathFilter.h" -#include "Gaffer/ScriptNode.h" #include "Gaffer/Private/IECorePreview/LRUCache.h" #include "IECorePython/RefCountedBinding.h" -#include "IECore/CamelCase.h" #include "IECore/StringAlgo.h" #include "boost/algorithm/string/predicate.hpp" @@ -75,7 +70,6 @@ using namespace GafferBindings; using namespace GafferUI; using namespace GafferScene; using namespace GafferSceneUI; -using namespace GafferSceneUI::Private; namespace { @@ -570,141 +564,6 @@ class RenderPassActiveColumn : public PathColumn StringDataPtr RenderPassActiveColumn::g_activeRenderPassIcon = new StringData( "activeRenderPass.png" ); StringDataPtr RenderPassActiveColumn::g_activeRenderPassFadedHighlightedIcon = new StringData( "activeRenderPassFadedHighlighted.png" ); -////////////////////////////////////////////////////////////////////////// -// OptionInspectorColumn -////////////////////////////////////////////////////////////////////////// - -/// \todo This map of SourceType colours is a duplicate of the one in LightEditorBinding.cpp. -/// We should consolidate these in the future. -const boost::container::flat_map g_sourceTypeColors = { - { (int)Inspector::Result::SourceType::Upstream, nullptr }, - { (int)Inspector::Result::SourceType::EditScope, new Color4fData( Imath::Color4f( 48, 100, 153, 150 ) / 255.0f ) }, - { (int)Inspector::Result::SourceType::Downstream, new Color4fData( Imath::Color4f( 239, 198, 24, 104 ) / 255.0f ) }, - { (int)Inspector::Result::SourceType::Other, nullptr }, - { (int)Inspector::Result::SourceType::Fallback, nullptr }, -}; -const Color4fDataPtr g_fallbackValueForegroundColor = new Color4fData( Imath::Color4f( 163, 163, 163, 255 ) / 255.0f ); - -class OptionInspectorColumn : public PathColumn -{ - - public : - - IE_CORE_DECLAREMEMBERPTR( OptionInspectorColumn ) - - OptionInspectorColumn( GafferSceneUI::Private::OptionInspectorPtr inspector, const std::string &columnName, const std::string &columnToolTip ) - : m_inspector( inspector ), m_headerValue( columnName != "" ? new StringData( columnName ) : headerValue( inspector->name() ) ), m_headerToolTip( new IECore::StringData( columnToolTip ) ) - { - m_inspector->dirtiedSignal().connect( boost::bind( &OptionInspectorColumn::inspectorDirtied, this ) ); - } - - GafferSceneUI::Private::Inspector *inspector() - { - return m_inspector.get(); - } - - CellData cellData( const Gaffer::Path &path, const IECore::Canceller *canceller ) const override - { - CellData result; - - auto renderPassPath = runTimeCast( &path ); - if( !renderPassPath ) - { - return result; - } - - const auto renderPassName = runTimeCast( path.property( g_renderPassNamePropertyName, canceller ) ); - if( !renderPassName ) - { - return result; - } - - Context::EditableScope scope( renderPassPath->getContext() ); - scope.setCanceller( canceller ); - scope.set( g_renderPassContextName, &( renderPassName->readable() ) ); - - Inspector::ConstResultPtr inspectorResult = m_inspector->inspect(); - if( !inspectorResult ) - { - return result; - } - - result.value = runTimeCast( inspectorResult->value() ); - /// \todo Should PathModel create a decoration automatically when we - /// return a colour for `Role::Value`? - result.icon = runTimeCast( inspectorResult->value() ); - result.background = g_sourceTypeColors.at( (int)inspectorResult->sourceType() ); - std::string toolTip; - if( inspectorResult->sourceType() == Inspector::Result::SourceType::Fallback ) - { - toolTip = "Source : " + inspectorResult->fallbackDescription(); - result.foreground = g_fallbackValueForegroundColor; - } - else if( const auto source = inspectorResult->source() ) - { - toolTip = "Source : " + source->relativeName( source->ancestor() ); - } - - if( inspectorResult->editable() ) - { - toolTip += !toolTip.empty() ? "\n\n" : ""; - if( runTimeCast( result.value ) ) - { - toolTip += "Double-click to toggle"; - } - else - { - toolTip += "Double-click to edit"; - } - } - - if( !toolTip.empty() ) - { - result.toolTip = new StringData( toolTip ); - } - - return result; - } - - CellData headerData( const IECore::Canceller *canceller ) const override - { - return CellData( m_headerValue, /* icon = */ nullptr, /* background = */ nullptr, m_headerToolTip ); - } - - private : - - void inspectorDirtied() - { - changedSignal()( this ); - } - - static IECore::ConstStringDataPtr headerValue( const std::string &inspectorName ) - { - std::string name = inspectorName; - // Convert from snake case and/or camel case to UI case. - if( name.find( '_' ) != std::string::npos ) - { - std::replace( name.begin(), name.end(), '_', ' ' ); - } - if( name.find( ' ' ) != std::string::npos ) - { - name = CamelCase::fromSpaced( name ); - } - return new StringData( CamelCase::toSpaced( name ) ); - } - - const OptionInspectorPtr m_inspector; - const ConstStringDataPtr m_headerValue; - const ConstStringDataPtr m_headerToolTip; - -}; - -PathColumn::CellData headerDataWrapper( PathColumn &pathColumn, const Canceller *canceller ) -{ - IECorePython::ScopedGILRelease gilRelease; - return pathColumn.headerData( canceller ); -} - ////////////////////////////////////////////////////////////////////////// // RenderPassEditorSearchFilter - filters based on a match pattern. This // removes non-leaf paths if all their children have also been @@ -912,18 +771,6 @@ void GafferSceneUIModule::bindRenderPassEditor() .def( init<>() ) ; - RefCountedClass( "OptionInspectorColumn" ) - .def( init( - ( - arg_( "inspector" ), - arg_( "columName" ) = "", - arg_( "columnToolTip" ) = "" - ) - ) ) - .def( "inspector", &OptionInspectorColumn::inspector, return_value_policy() ) - .def( "headerData", &headerDataWrapper, ( arg_( "canceller" ) = object() ) ) - ; - RefCountedClass( "SearchFilter" ) .def( init( ( boost::python::arg( "userData" ) = object() ) ) ) .def( "setMatchPattern", &RenderPassEditorSearchFilter::setMatchPattern ) From 4324a8b0d317a6dce8f6d4243fcb1691dae702be Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:13:20 -0700 Subject: [PATCH 7/9] RenderPassEditor : Use inspectionContext Replace contexts with hard-coded references to `renderPass` with calls to the path's `inspectionContext()`. The only hard-coded reference to `renderPass` remaining is in the HistoryWindow title, though a generic approach to displaying histories could replace this with `path`... --- python/GafferSceneUI/RenderPassEditor.py | 94 ++++++++++++------------ 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/python/GafferSceneUI/RenderPassEditor.py b/python/GafferSceneUI/RenderPassEditor.py index e632e1a5617..85897766dde 100644 --- a/python/GafferSceneUI/RenderPassEditor.py +++ b/python/GafferSceneUI/RenderPassEditor.py @@ -385,32 +385,30 @@ def __setActiveRenderPass( self, pathListing ) : renderPassPlug["value"].setValue( selectedPassNames[0] if selectedPassNames[0] != currentRenderPass else "" ) - ## \todo Consider consolidating this with `LightEditor.__editSelectedCells()`. - # The main difference being the name of the context variable that is set before inspection, - # (`renderPass` vs `scene:path`) and the source of data for that variable. + ## \todo Replace this and `LightEditor.__editSelectedCells()` with common editing + # functionality to be provided by InspectorColumn in the future. def __editSelectedCells( self, pathListing, quickBoolean = True ) : # A dictionary of the form : - # { inspector : { renderPass1 : inspection, renderPass2 : inspection, ... }, ... } + # { inspector : { path1 : inspection, path2 : inspection, ... }, ... } inspectors = {} inspections = [] - with Gaffer.Context( self.context() ) as context : - renderPassPath = self.__pathListing.getPath().copy() - for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + path = pathListing.getPath().copy() + for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + continue + for pathString in selection.paths() : + path.setFromString( pathString ) + inspectionContext = path.inspectionContext() + if inspectionContext is None : continue - for path in selection.paths() : - renderPassPath.setFromString( path ) - renderPassName = renderPassPath.property( "renderPassPath:name" ) - if not renderPassName : - continue - context["renderPass"] = renderPassName + with inspectionContext : inspection = column.inspector().inspect() if inspection is not None : - inspectors.setdefault( column.inspector(), {} )[renderPassName] = inspection + inspectors.setdefault( column.inspector(), {} )[pathString] = inspection inspections.append( inspection ) if len( inspectors ) == 0 : @@ -496,18 +494,17 @@ def __disablableInspectionTweaks( self, pathListing ) : tweaks = [] - with Gaffer.Context( self.context() ) as context : - renderPassPath = self.__pathListing.getPath().copy() - for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + path = pathListing.getPath().copy() + for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + continue + for pathString in columnSelection.paths() : + path.setFromString( pathString ) + inspectionContext = path.inspectionContext() + if inspectionContext is None : continue - for path in columnSelection.paths() : - renderPassPath.setFromString( path ) - renderPassName = renderPassPath.property( "renderPassPath:name" ) - if not renderPassName : - continue - context["renderPass"] = renderPassName + with inspectionContext : inspection = column.inspector().inspect() if inspection is not None and inspection.editable() : source = inspection.source() @@ -519,7 +516,7 @@ def __disablableInspectionTweaks( self, pathListing ) : ) and ( editScope is None or editScope.node().isAncestorOf( source ) ) ) : - tweaks.append( ( path, column.inspector() ) ) + tweaks.append( ( pathString, column.inspector() ) ) else : return [] else : @@ -531,22 +528,22 @@ def __disableEdits( self, pathListing ) : edits = self.__disablableInspectionTweaks( pathListing ) - with Gaffer.UndoScope( self.scriptNode() ), Gaffer.Context( self.context() ) as context : - renderPassPath = self.__pathListing.getPath().copy() - for path, inspector in edits : - renderPassPath.setFromString( path ) - context["renderPass"] = renderPassPath.property( "renderPassPath:name" ) - inspection = inspector.inspect() - if inspection is not None and inspection.editable() : - source = inspection.source() + path = pathListing.getPath().copy() + with Gaffer.UndoScope( self.scriptNode() ) : + for pathString, inspector in edits : + path.setFromString( pathString ) + with path.inspectionContext() : + inspection = inspector.inspect() + if inspection is not None and inspection.editable() : + source = inspection.source() - if isinstance( source, ( Gaffer.TweakPlug, Gaffer.NameValuePlug ) ) : - source["enabled"].setValue( False ) + if isinstance( source, ( Gaffer.TweakPlug, Gaffer.NameValuePlug ) ) : + source["enabled"].setValue( False ) def __selectedSetExpressions( self, pathListing ) : # A dictionary of the form : - # { renderPass1 : set( setExpression1, setExpression2 ), renderPass2 : set( setExpression1 ), ... } + # { path1 : set( setExpression1, setExpression2 ), path2 : set( setExpression1 ), ... } result = {} renderPassPath = pathListing.getPath().copy() @@ -569,7 +566,7 @@ def __selectedSetExpressions( self, pathListing ) : renderPassPath.setFromString( path ) cellValue = column.cellData( renderPassPath ).value if cellValue is not None : - result.setdefault( renderPassPath.property( "renderPassPath:name" ), set() ).add( cellValue ) + result.setdefault( path, set() ).add( cellValue ) else : # We only return set expressions if all selected paths are render passes. return {} @@ -580,11 +577,12 @@ def __selectAffected( self, pathListing ) : result = IECore.PathMatcher() - with Gaffer.Context( self.context() ) as context : - for renderPass, setExpressions in self.__selectedSetExpressions( pathListing ).items() : - # Evaluate set expressions within their render pass in the context - # as set membership could vary based on the render pass. - context["renderPass"] = renderPass + renderPassPath = pathListing.getPath().copy() + for path, setExpressions in self.__selectedSetExpressions( pathListing ).items() : + # Evaluate set expressions within their render pass in the context + # as set membership could vary based on the render pass. + renderPassPath.setFromString( path ) + with renderPassPath.inspectionContext() : for setExpression in setExpressions : result.addPaths( GafferScene.SetAlgo.evaluateSetExpression( setExpression, self.settings()["in"] ) ) @@ -680,18 +678,16 @@ def __showEditHistory( self, *unused ) : for path in selection[i].paths() : renderPassPath.setFromString( path ) - renderPassName = renderPassPath.property( "renderPassPath:name" ) - if renderPassName is None : + inspectionContext = renderPassPath.inspectionContext() + if inspectionContext is None : continue - historyContext = Gaffer.Context( self.context() ) - historyContext["renderPass"] = renderPassName window = _HistoryWindow( column.inspector(), "/", - historyContext, + inspectionContext, self.ancestor( GafferUI.ScriptWindow ).scriptNode(), - "History : {} : {}".format( renderPassName, column.headerData().value ) + "History : {} : {}".format( inspectionContext["renderPass"], column.headerData().value ) ) self.ancestor( GafferUI.Window ).addChildWindow( window, removeOnClose = True ) window.setVisible( True ) From 9ff42486b3925eb5217de66d5c0e04e1a6523094 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:14:06 -0700 Subject: [PATCH 8/9] LightEditor : Use InspectorColumn Remove the LightEditor specific implementation of InspectorColumn and replace with the general purpose GafferSceneUI::Private::InspectorColumn. --- Changes.md | 3 + python/GafferSceneUI/LightEditor.py | 18 +-- python/GafferSceneUITest/LightEditorTest.py | 2 +- .../LightEditorBinding.cpp | 132 +----------------- 4 files changed, 16 insertions(+), 139 deletions(-) diff --git a/Changes.md b/Changes.md index ed3783a9418..822292ba3e4 100644 --- a/Changes.md +++ b/Changes.md @@ -16,6 +16,9 @@ Improvements - Improved highlighting of active nodes, with more accurate tracking of Loop node iterations. - Annotation `{plug}` substitutions are now evaluated in a context determined relative to the focus node. - The strike-through for disabled nodes is now evaluated in a context determined relative to the focus node. +- LightEditor : + - Improved formatting of column headers containing whitespace. + - The "Double-click to toggle" tooltip is no longer displayed while hovering over non-editable cells, and a "Double-click to edit" tooltip is now displayed while hovering over other non-toggleable but editable cells. Fixes ----- diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index bc9c1d3d179..fa489067145 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -154,7 +154,7 @@ def registerParameter( cls, rendererKey, parameter, section = None, columnName = GafferSceneUI.LightEditor.registerColumn( rendererKey, ".".join( x for x in [ parameter.shader, parameter.name ] if x ), - lambda scene, editScope : _GafferSceneUI._LightEditorInspectorColumn( + lambda scene, editScope : GafferSceneUI.Private.InspectorColumn( GafferSceneUI.Private.ParameterInspector( scene, editScope, rendererKey, parameter ), columnName if columnName is not None else "" ), @@ -175,7 +175,7 @@ def registerShaderParameter( cls, rendererKey, parameter, shaderAttribute = None GafferSceneUI.LightEditor.registerColumn( rendererKey, ".".join( x for x in [ parameter.shader, parameter.name ] if x ), - lambda scene, editScope : _GafferSceneUI._LightEditorInspectorColumn( + lambda scene, editScope : GafferSceneUI.Private.InspectorColumn( GafferSceneUI.Private.ParameterInspector( scene, editScope, shaderAttribute, parameter ), columnName if columnName is not None else "" ), @@ -189,7 +189,7 @@ def registerAttribute( cls, rendererKey, attributeName, section = None ) : GafferSceneUI.LightEditor.registerColumn( rendererKey, attributeName, - lambda scene, editScope : _GafferSceneUI._LightEditorInspectorColumn( + lambda scene, editScope : GafferSceneUI.Private.InspectorColumn( GafferSceneUI.Private.AttributeInspector( scene, editScope, attributeName ), displayName ), @@ -199,7 +199,7 @@ def registerAttribute( cls, rendererKey, attributeName, section = None ) : # Registers a column in the Light Editor. # `inspectorFunction` is a callable object of the form # `inspectorFunction( scene, editScope )` returning a - # `GafferSceneUI._LightEditorInspectorColumn` object. + # `GafferSceneUI.Private.InspectorColumn` object. @classmethod def registerColumn( cls, rendererKey, columnKey, inspectorFunction, section = None ) : @@ -336,7 +336,7 @@ def __editSelectedCells( self, pathListing, quickBoolean = True ) : with Gaffer.Context( self.context() ) as context : for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for pathString in selection.paths() : path = GafferScene.ScenePlug.stringToPath( pathString ) @@ -441,7 +441,7 @@ def __disablableInspectionTweaks( self, pathListing ) : with Gaffer.Context( self.context() ) as context : for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for path in columnSelection.paths() : context["scene:path"] = GafferScene.ScenePlug.stringToPath( path ) @@ -490,7 +490,7 @@ def __removableAttributeInspections( self, pathListing ) : with Gaffer.Context( self.context() ) as context : for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue elif not columnSelection.isEmpty() and type( column.inspector() ) != GafferSceneUI.Private.AttributeInspector : return [] @@ -533,7 +533,7 @@ def __selectedSetExpressions( self, pathListing ) : for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : if ( not columnSelection.isEmpty() and ( - not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) or + not isinstance( column, GafferSceneUI.Private.InspectorColumn ) or not ( Gaffer.Metadata.value( "attribute:" + column.inspector().name(), "ui:scene:acceptsSetName" ) or Gaffer.Metadata.value( "attribute:" + column.inspector().name(), "ui:scene:acceptsSetNames" ) or @@ -726,7 +726,7 @@ def __showHistory( self, *unused ) : for i in range( 0, len( columns ) ) : column = columns[ i ] - if not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : continue for path in selection[i].paths() : diff --git a/python/GafferSceneUITest/LightEditorTest.py b/python/GafferSceneUITest/LightEditorTest.py index fbbc7be7cbd..18938f369dd 100644 --- a/python/GafferSceneUITest/LightEditorTest.py +++ b/python/GafferSceneUITest/LightEditorTest.py @@ -751,7 +751,7 @@ def testShaderParameterEditScope( self ) : addAInspector = None exposureInspector = None for c in widget.getColumns() : - if not isinstance( c, _GafferSceneUI._LightEditorInspectorColumn ) : + if not isinstance( c, GafferSceneUI.Private.InspectorColumn ) : continue if c.headerData().value == "A" : addAInspector = c.inspector() diff --git a/src/GafferSceneUIModule/LightEditorBinding.cpp b/src/GafferSceneUIModule/LightEditorBinding.cpp index 1734a6dfe9a..df5dd3ae9f0 100644 --- a/src/GafferSceneUIModule/LightEditorBinding.cpp +++ b/src/GafferSceneUIModule/LightEditorBinding.cpp @@ -40,6 +40,7 @@ #include "GafferSceneUI/Private/AttributeInspector.h" #include "GafferSceneUI/Private/Inspector.h" +#include "GafferSceneUI/Private/InspectorColumn.h" #include "GafferSceneUI/Private/SetMembershipInspector.h" #include "GafferScene/ScenePath.h" @@ -170,112 +171,6 @@ class LocationNameColumn : public StandardPathColumn }; -const boost::container::flat_map g_sourceTypeColors = { - { (int)Inspector::Result::SourceType::Upstream, nullptr }, - { (int)Inspector::Result::SourceType::EditScope, new Color4fData( Imath::Color4f( 48, 100, 153, 150 ) / 255.0f ) }, - { (int)Inspector::Result::SourceType::Downstream, new Color4fData( Imath::Color4f( 239, 198, 24, 104 ) / 255.0f ) }, - { (int)Inspector::Result::SourceType::Other, nullptr }, - { (int)Inspector::Result::SourceType::Fallback, nullptr }, -}; -const Color4fDataPtr g_fallbackValueForegroundColor = new Color4fData( Imath::Color4f( 163, 163, 163, 255 ) / 255.0f ); - -class InspectorColumn : public PathColumn -{ - - public : - - IE_CORE_DECLAREMEMBERPTR( InspectorColumn ) - - InspectorColumn( GafferSceneUI::Private::InspectorPtr inspector, const std::string &columnName ) - : m_inspector( inspector ), m_headerValue( headerValue( columnName != "" ? columnName : inspector->name() ) ) - { - m_inspector->dirtiedSignal().connect( boost::bind( &InspectorColumn::inspectorDirtied, this ) ); - } - - GafferSceneUI::Private::Inspector *inspector() const - { - return m_inspector.get(); - } - - CellData cellData( const Gaffer::Path &path, const IECore::Canceller *canceller ) const override - { - CellData result; - - auto scenePath = runTimeCast( &path ); - if( !scenePath ) - { - return result; - } - - ScenePlug::PathScope scope( scenePath->getContext(), &scenePath->names() ); - scope.setCanceller( canceller ); - - Inspector::ConstResultPtr inspectorResult = m_inspector->inspect(); - if( !inspectorResult ) - { - return result; - } - - result.value = runTimeCast( inspectorResult->value() ); - /// \todo Should PathModel create a decoration automatically when we - /// return a colour for `Role::Value`? - result.icon = runTimeCast( inspectorResult->value() ); - result.background = g_sourceTypeColors.at( (int)inspectorResult->sourceType() ); - std::string toolTip; - if( inspectorResult->sourceType() == Inspector::Result::SourceType::Fallback ) - { - toolTip = "Source : " + inspectorResult->fallbackDescription(); - result.foreground = g_fallbackValueForegroundColor; - } - else if( auto source = inspectorResult->source() ) - { - toolTip = "Source : " + source->relativeName( source->ancestor() ); - } - - if( runTimeCast( result.value ) ) - { - toolTip += !toolTip.empty() ? "\n\nDouble-click to toggle" : "Double-click to toggle"; - } - - if( !toolTip.empty() ) - { - result.toolTip = new StringData( toolTip ); - } - - return result; - } - - CellData headerData( const IECore::Canceller *canceller ) const override - { - return CellData( m_headerValue ); - } - - private : - - void inspectorDirtied() - { - changedSignal()( this ); - } - - static IECore::ConstStringDataPtr headerValue( const std::string &inspectorName ) - { - std::string name = inspectorName; - // Convert from snake case and/or camel case to UI case. - if( name.find( '_' ) != std::string::npos ) - { - std::replace( name.begin(), name.end(), '_', ' ' ); - name = CamelCase::fromSpaced( name ); - } - return new StringData( CamelCase::toSpaced( name ) ); - } - - const InspectorPtr m_inspector; - const ConstStringDataPtr m_headerValue; - -}; - -/// \todo `MuteColumn` and `SetMembershipColumn` should not exist and we intend -/// to continue refactoring until it is possible to remove them entirely. class MuteColumn : public InspectorColumn { @@ -479,12 +374,6 @@ CompoundDataPtr SetMembershipColumn::m_setMemberUndefinedIconData = new Compound StringDataPtr SetMembershipColumn::m_setHasMembers = new StringData( "setMember.png" ); StringDataPtr SetMembershipColumn::m_setEmpty = new StringData( "muteLightUndefined.png" ); -PathColumn::CellData headerDataWrapper( PathColumn &pathColumn, const Canceller *canceller ) -{ - IECorePython::ScopedGILRelease gilRelease; - return pathColumn.headerData( canceller ); -} - } // namespace ////////////////////////////////////////////////////////////////////////// @@ -498,29 +387,16 @@ void GafferSceneUIModule::bindLightEditor() .def( init<>() ) ; - IECorePython::RefCountedClass( "_LightEditorInspectorColumn" ) - .def( init( - ( - arg_( "inspector" ), - arg_( "columName" ) = "" - ) - ) ) - .def( "inspector", &InspectorColumn::inspector, return_value_policy() ) - .def( "headerData", &headerDataWrapper, ( arg_( "canceller" ) = object() ) ) - ; - - IECorePython::RefCountedClass( "_LightEditorMuteColumn" ) + IECorePython::RefCountedClass( "_LightEditorMuteColumn" ) .def( init( ( arg_( "scene" ), arg_( "editScope" ) ) ) ) - .def( "inspector", &MuteColumn::inspector, return_value_policy() ) - .def( "headerData", &headerDataWrapper, ( arg_( "canceller" ) = object() ) ) ; - IECorePython::RefCountedClass( "_LightEditorSetMembershipColumn" ) + IECorePython::RefCountedClass( "_LightEditorSetMembershipColumn" ) .def( init( ( arg_( "scene" ), @@ -529,8 +405,6 @@ void GafferSceneUIModule::bindLightEditor() arg_( "columnName" ) ) ) ) - .def( "inspector", &SetMembershipColumn::inspector, return_value_policy() ) - .def( "headerData", &headerDataWrapper, ( arg_( "canceller" ) = object() ) ) ; } From dbf8694bd5468c7f318c57ba1a8d36133cbf372b Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:14:17 -0700 Subject: [PATCH 9/9] LightEditor : Use inspectionContext Replace contexts with hard-coded references to `scene:path` with calls to the path's `inspectionContext()`. --- python/GafferSceneUI/LightEditor.py | 73 +++++++++++---------- python/GafferSceneUITest/LightEditorTest.py | 9 +++ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index fa489067145..78b2710775a 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -333,19 +333,17 @@ def __editSelectedCells( self, pathListing, quickBoolean = True ) : inspectors = {} inspections = [] - with Gaffer.Context( self.context() ) as context : - - for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : - continue - for pathString in selection.paths() : - path = GafferScene.ScenePlug.stringToPath( pathString ) - - context["scene:path"] = path + path = pathListing.getPath().copy() + for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + continue + for pathString in selection.paths() : + path.setFromString( pathString ) + with path.inspectionContext() : inspection = column.inspector().inspect() if inspection is not None : - inspectors.setdefault( column.inspector(), {} )[path] = inspection + inspectors.setdefault( column.inspector(), {} )[pathString] = inspection inspections.append( inspection ) if len( inspectors ) == 0 : @@ -439,12 +437,13 @@ def __disablableInspectionTweaks( self, pathListing ) : tweaks = [] - with Gaffer.Context( self.context() ) as context : - for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : - continue - for path in columnSelection.paths() : - context["scene:path"] = GafferScene.ScenePlug.stringToPath( path ) + path = pathListing.getPath().copy() + for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + continue + for pathString in columnSelection.paths() : + path.setFromString( pathString ) + with path.inspectionContext() : inspection = column.inspector().inspect() if inspection is not None and inspection.editable() : source = inspection.source() @@ -459,7 +458,7 @@ def __disablableInspectionTweaks( self, pathListing ) : ) and ( editScope is None or editScope.node().isAncestorOf( source ) ) ) : - tweaks.append( ( path, column.inspector() ) ) + tweaks.append( ( pathString, column.inspector() ) ) else : return [] else : @@ -471,31 +470,33 @@ def __disableEdits( self, pathListing ) : edits = self.__disablableInspectionTweaks( pathListing ) - with Gaffer.UndoScope( self.scriptNode() ), Gaffer.Context( self.context() ) as context : - for path, inspector in edits : - context["scene:path"] = GafferScene.ScenePlug.stringToPath( path ) - - inspection = inspector.inspect() - if inspection is not None and inspection.editable() : - source = inspection.source() + path = pathListing.getPath().copy() + with Gaffer.UndoScope( self.scriptNode() ) : + for pathString, inspector in edits : + path.setFromString( pathString ) + with path.inspectionContext() : + inspection = inspector.inspect() + if inspection is not None and inspection.editable() : + source = inspection.source() - if isinstance( source, ( Gaffer.TweakPlug, Gaffer.NameValuePlug ) ) : - source["enabled"].setValue( False ) - elif isinstance( inspector, GafferSceneUI.Private.SetMembershipInspector ) : - inspector.editSetMembership( inspection, path, GafferScene.EditScopeAlgo.SetMembership.Unchanged ) + if isinstance( source, ( Gaffer.TweakPlug, Gaffer.NameValuePlug ) ) : + source["enabled"].setValue( False ) + elif isinstance( inspector, GafferSceneUI.Private.SetMembershipInspector ) : + inspector.editSetMembership( inspection, pathString, GafferScene.EditScopeAlgo.SetMembership.Unchanged ) def __removableAttributeInspections( self, pathListing ) : inspections = [] - with Gaffer.Context( self.context() ) as context : - for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : - if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : - continue - elif not columnSelection.isEmpty() and type( column.inspector() ) != GafferSceneUI.Private.AttributeInspector : - return [] - for path in columnSelection.paths() : - context["scene:path"] = GafferScene.ScenePlug.stringToPath( path ) + path = pathListing.getPath().copy() + for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if not isinstance( column, GafferSceneUI.Private.InspectorColumn ) : + continue + elif not columnSelection.isEmpty() and type( column.inspector() ) != GafferSceneUI.Private.AttributeInspector : + return [] + for pathString in columnSelection.paths() : + path.setFromString( pathString ) + with path.inspectionContext() : inspection = column.inspector().inspect() if inspection is not None and inspection.editable() : source = inspection.source() diff --git a/python/GafferSceneUITest/LightEditorTest.py b/python/GafferSceneUITest/LightEditorTest.py index 18938f369dd..a875a739619 100644 --- a/python/GafferSceneUITest/LightEditorTest.py +++ b/python/GafferSceneUITest/LightEditorTest.py @@ -652,6 +652,10 @@ def testLightMuteAttribute( toggleCount, toggleLocation, newStates ) : editor._LightEditor__updateColumns() GafferSceneUI.LightEditor._LightEditor__updateColumns.flush( editor ) + editor.setNodeSet( Gaffer.StandardSet( [ script["editScope"] ] ) ) + editor._LightEditor__setPathListingPath() + GafferSceneUI.LightEditor._LightEditor__setPathListingPath.flush( editor ) + widget = editor._LightEditor__pathListing self.setLightEditorMuteSelection( widget, togglePaths ) @@ -704,6 +708,9 @@ def testToggleContext( self ) : widget = editor._LightEditor__pathListing editor.setNodeSet( Gaffer.StandardSet( [ script["custAttr"] ] ) ) + editor._LightEditor__setPathListingPath() + GafferSceneUI.LightEditor._LightEditor__setPathListingPath.flush( editor ) + self.setLightEditorMuteSelection( widget, ["/group/light"] ) # This will raise an exception if the context is not scoped correctly. @@ -712,6 +719,8 @@ def testToggleContext( self ) : True # quickBoolean ) + del widget, editor + def testShaderParameterEditScope( self ) : GafferSceneUI.LightEditor.registerShaderParameter( "light", "add.a" )