diff --git a/Changes.md b/Changes.md
index 42dae03d5f..719dc828e2 100644
--- a/Changes.md
+++ b/Changes.md
@@ -28,6 +28,7 @@ Improvements
- Spreadsheet : Added yellow underlining to the currently active row.
- PlugLayout : Summaries and activators are now evaluated in a context determined relative to the focus node.
- Editor : The node graph is now evaluated in a context determined relative to the focus node.
+- LightEditor, RenderPassEditor : The "Disable Edit" right-click menu item and D shortcut now act as a toggle, where edits disabled in the current session via these actions can be reenabled with D or by selecting "Reenable Edit" from the right-click menu.
Fixes
-----
@@ -55,6 +56,7 @@ Fixes
- FreezeTransform : Constant primitive variables with point/vector interpretations are now also transformed.
- usdview : Added Windows support (#5599).
- ContextTracker : Removed unnecessary reference increment/decrement from `isTracked()`, `context()` and `isEnabled()`.
+- Menu : Fixed bug causing a menu item's tooltip to not hide when moving the cursor to another menu item without a tooltip.
API
---
diff --git a/include/GafferSceneUI/Private/AttributeInspector.h b/include/GafferSceneUI/Private/AttributeInspector.h
index 394982de0d..f1749a78c4 100644
--- a/include/GafferSceneUI/Private/AttributeInspector.h
+++ b/include/GafferSceneUI/Private/AttributeInspector.h
@@ -65,9 +65,9 @@ class GAFFERSCENEUI_API AttributeInspector : public Inspector
GafferScene::SceneAlgo::History::ConstPtr history() const override;
IECore::ConstObjectPtr value( const GafferScene::SceneAlgo::History *history) const override;
+ IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
Gaffer::ValuePlugPtr source( const GafferScene::SceneAlgo::History *history, std::string &editWarning ) const override;
EditFunctionOrFailure editFunction( Gaffer::EditScope *scope, const GafferScene::SceneAlgo::History *history) const override;
- IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
bool attributeExists() const;
diff --git a/include/GafferSceneUI/Private/Inspector.h b/include/GafferSceneUI/Private/Inspector.h
index 678e1c2e0b..04f4e9c224 100644
--- a/include/GafferSceneUI/Private/Inspector.h
+++ b/include/GafferSceneUI/Private/Inspector.h
@@ -170,7 +170,7 @@ class GAFFERSCENEUI_API Inspector : public IECore::RefCounted, public Gaffer::Si
/// history class?
virtual Gaffer::ValuePlugPtr source( const GafferScene::SceneAlgo::History *history, std::string &editWarning ) const;
- using EditFunction = std::function;
+ using EditFunction = std::function;
using EditFunctionOrFailure = boost::variant;
/// Should be implemented to return a function that will acquire
/// an edit from the EditScope at the specified point in the history.
@@ -183,6 +183,14 @@ class GAFFERSCENEUI_API Inspector : public IECore::RefCounted, public Gaffer::Si
/// > that edits the processor itself.
virtual EditFunctionOrFailure editFunction( Gaffer::EditScope *editScope, const GafferScene::SceneAlgo::History *history ) const;
+ using DisableEditFunction = std::function;
+ using DisableEditFunctionOrFailure = boost::variant;
+ /// Can be implemented to return a function that will disable an edit
+ /// at the specified plug. If this is not possible, should return an
+ /// error explaining why (this is typically due to `readOnly` metadata).
+ /// Called with `history->context` as the current context.
+ virtual DisableEditFunctionOrFailure disableEditFunction( Gaffer::ValuePlug *plug, const GafferScene::SceneAlgo::History *history ) const;
+
protected :
Gaffer::EditScope *targetEditScope() const;
@@ -338,11 +346,21 @@ class GAFFERSCENEUI_API Inspector::Result : public IECore::RefCounted
/// Returns a plug that can be used to edit the property
/// represented by this inspector, creating it if necessary.
/// Throws if `!editable()`.
- Gaffer::ValuePlugPtr acquireEdit() const;
+ Gaffer::ValuePlugPtr acquireEdit( bool createIfNecessary = true ) const;
/// Returns a warning associated with the plug returned
/// by `acquireEdit()`. This should be displayed to the user.
std::string editWarning() const;
+ /// Returns `true` if `disableEdit()` will disable the edit
+ /// at `source()`, and `false` otherwise.
+ bool canDisableEdit() const;
+ /// If `canDisableEdit()` returns false, returns the reason why.
+ /// This should be displayed to the user.
+ std::string nonDisableableReason() const;
+ /// Disables the edit at `source()`. Throws if
+ /// `!canDisableEdit()`
+ void disableEdit() const;
+
private :
Result( const IECore::ConstObjectPtr &value, const Gaffer::EditScopePtr &editScope );
@@ -359,6 +377,8 @@ class GAFFERSCENEUI_API Inspector::Result : public IECore::RefCounted
EditFunctionOrFailure m_editFunction;
std::string m_editWarning;
+ DisableEditFunctionOrFailure m_disableEditFunction;
+
};
} // namespace Private
diff --git a/include/GafferSceneUI/Private/OptionInspector.h b/include/GafferSceneUI/Private/OptionInspector.h
index 4fd1dd7957..73a42b6b52 100644
--- a/include/GafferSceneUI/Private/OptionInspector.h
+++ b/include/GafferSceneUI/Private/OptionInspector.h
@@ -63,9 +63,9 @@ class GAFFERSCENEUI_API OptionInspector : public Inspector
GafferScene::SceneAlgo::History::ConstPtr history() const override;
IECore::ConstObjectPtr value( const GafferScene::SceneAlgo::History *history ) const override;
+ IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
Gaffer::ValuePlugPtr source( const GafferScene::SceneAlgo::History *history, std::string &editWarning ) const override;
EditFunctionOrFailure editFunction( Gaffer::EditScope *scope, const GafferScene::SceneAlgo::History *history ) const override;
- IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
private :
diff --git a/include/GafferSceneUI/Private/ParameterInspector.h b/include/GafferSceneUI/Private/ParameterInspector.h
index 0ba4f86762..ad9680e8b2 100644
--- a/include/GafferSceneUI/Private/ParameterInspector.h
+++ b/include/GafferSceneUI/Private/ParameterInspector.h
@@ -67,9 +67,9 @@ class GAFFERSCENEUI_API ParameterInspector : public AttributeInspector
private :
IECore::ConstObjectPtr value( const GafferScene::SceneAlgo::History *history ) const override;
+ IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
Gaffer::ValuePlugPtr source( const GafferScene::SceneAlgo::History *history, std::string &editWarning ) const override;
EditFunctionOrFailure editFunction( Gaffer::EditScope *editScope, const GafferScene::SceneAlgo::History *history ) const override;
- IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
const IECoreScene::ShaderNetwork::Parameter m_parameter;
diff --git a/include/GafferSceneUI/Private/SetMembershipInspector.h b/include/GafferSceneUI/Private/SetMembershipInspector.h
index 51622c6db9..b06507e064 100644
--- a/include/GafferSceneUI/Private/SetMembershipInspector.h
+++ b/include/GafferSceneUI/Private/SetMembershipInspector.h
@@ -75,13 +75,14 @@ class GAFFERSCENEUI_API SetMembershipInspector : public Inspector
GafferScene::SceneAlgo::History::ConstPtr history() const override;
IECore::ConstObjectPtr value( const GafferScene::SceneAlgo::History *history) const override;
+ IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
/// For the given `history`, returns either the "sets" `StringPlug` of an `ObjectSource`
/// node, the "name" `StringPlug` of a `Set` node, the `Spreadsheet::RowPlug` for the
/// appropriate row of a set membership processor spreadsheet or `nullptr` if none of
/// those are found.
Gaffer::ValuePlugPtr source( const GafferScene::SceneAlgo::History *history, std::string &editWarning ) const override;
EditFunctionOrFailure editFunction( Gaffer::EditScope *scope, const GafferScene::SceneAlgo::History *history ) const override;
- IECore::ConstObjectPtr fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const override;
+ DisableEditFunctionOrFailure disableEditFunction( Gaffer::ValuePlug *plug, const GafferScene::SceneAlgo::History *history ) const override;
private :
diff --git a/python/GafferSceneUI/_InspectorColumn.py b/python/GafferSceneUI/_InspectorColumn.py
index 7001498931..490b954f9f 100644
--- a/python/GafferSceneUI/_InspectorColumn.py
+++ b/python/GafferSceneUI/_InspectorColumn.py
@@ -162,9 +162,11 @@ def __editSelectedCells( pathListing, quickBoolean = True ) :
__inspectorColumnPopup.popup( parent = pathListing )
-def __disablableInspectionTweaks( pathListing ) :
+def __toggleableInspections( pathListing ) :
- tweaks = []
+ inspections = []
+ nonEditableReason = ""
+ toggleShouldDisable = True
path = pathListing.getPath().copy()
for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) :
@@ -176,43 +178,55 @@ def __disablableInspectionTweaks( pathListing ) :
with inspectionContext :
inspection = column.inspector().inspect()
- if inspection is not None and inspection.editable() :
- source = inspection.source()
- editScope = inspection.editScope()
- if (
- (
- (
- isinstance( source, ( Gaffer.TweakPlug, Gaffer.NameValuePlug ) ) and
- source["enabled"].getValue()
- ) or
- isinstance( column.inspector(), GafferSceneUI.Private.SetMembershipInspector )
- ) and
- ( editScope is None or editScope.isAncestorOf( source ) )
- ) :
- tweaks.append( ( pathString, column.inspector() ) )
- else :
- return []
- else :
- return []
+ if inspection is None :
+ continue
- return tweaks
+ canReenableEdit = False
+ if not inspection.canDisableEdit() and inspection.editable() :
+ edit = inspection.acquireEdit( createIfNecessary = False )
+ canReenableEdit = isinstance( edit, ( Gaffer.NameValuePlug, Gaffer.OptionalValuePlug, Gaffer.TweakPlug ) ) and Gaffer.Metadata.value( edit, "inspector:disabledEdit" )
+ if canReenableEdit :
+ toggleShouldDisable = False
-def __disableEdits( pathListing ) :
+ if canReenableEdit or inspection.canDisableEdit() :
+ inspections.append( inspection )
+ elif nonEditableReason == "" :
+ # Prefix reason with the column header to disambiguate when more than one column has selection
+ nonEditableReason = "{} : ".format( column.headerData().value ) if len( [ x for x in pathListing.getSelection() if not x.isEmpty() ] ) > 1 else ""
+ nonEditableReason += inspection.nonDisableableReason() if toggleShouldDisable else inspection.nonEditableReason()
- edits = __disablableInspectionTweaks( pathListing )
- path = pathListing.getPath().copy()
- with Gaffer.UndoScope( pathListing.ancestor( GafferUI.Editor ).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()
+ return inspections, nonEditableReason, toggleShouldDisable
- 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 __toggleEditEnabled( pathListing ) :
+
+ global __inspectorColumnPopup
+
+ inspections, nonEditableReason, shouldDisable = __toggleableInspections( pathListing )
+
+ if nonEditableReason != "" :
+ with GafferUI.PopupWindow() as __inspectorColumnPopup :
+ with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) :
+ GafferUI.Image( "warningSmall.png" )
+ GafferUI.Label( "{}
".format( nonEditableReason ) )
+
+ __inspectorColumnPopup.popup( parent = pathListing )
+
+ return
+
+ with Gaffer.UndoScope( pathListing.ancestor( GafferUI.Editor ).scriptNode() ) :
+ for inspection in inspections :
+ if shouldDisable :
+ inspection.disableEdit()
+ # We register non-persistent metadata on disabled edits to later determine
+ # whether the disabled edit is a suitable candidate for enabling. This allows
+ # investigative toggling of edits in the current session while avoiding enabling
+ # edits the user may not expect to exist, such as previously unedited spreadsheet
+ # cells in EditScope processors.
+ Gaffer.Metadata.registerValue( inspection.source(), "inspector:disabledEdit", True, persistent = False )
+ else :
+ edit = inspection.acquireEdit( createIfNecessary = False )
+ edit["enabled"].setValue( True )
+ Gaffer.Metadata.deregisterValue( edit, "inspector:disabledEdit" )
def __removableAttributeInspections( pathListing ) :
@@ -385,12 +399,14 @@ def __contextMenu( column, pathListing, menuDefinition ) :
"command" : functools.partial( __editSelectedCells, pathListing, toggleOnly ),
}
)
+ inspections, nonEditableReason, disable = __toggleableInspections( pathListing )
menuDefinition.append(
- "Disable Edit",
+ "{} Edit{}".format( "Disable" if disable else "Reenable", "s" if len( inspections ) > 1 else "" ),
{
- "command" : functools.partial( __disableEdits, pathListing ),
- "active" : len( __disablableInspectionTweaks( pathListing ) ) > 0,
+ "command" : functools.partial( __toggleEditEnabled, pathListing ),
+ "active" : len( inspections ) > 0 and nonEditableReason == "",
"shortCut" : "D",
+ "description" : nonEditableReason,
}
)
@@ -427,8 +443,11 @@ def __keyPress( column, pathListing, event ) :
return True
if event.key == "D" :
- if len( __disablableInspectionTweaks( pathListing ) ) > 0 :
- __disableEdits( pathListing )
+ inspections, nonEditableReason, _ = __toggleableInspections( pathListing )
+ # We allow toggling when there is a nonEditableReason to let __toggleEditEnabled
+ # present the reason to the user via a popup.
+ if len( inspections ) > 0 or nonEditableReason != "" :
+ __toggleEditEnabled( pathListing )
return True
if event.key in ( "Backspace", "Delete" ) :
diff --git a/python/GafferSceneUITest/AttributeInspectorTest.py b/python/GafferSceneUITest/AttributeInspectorTest.py
index 63846f4dbd..2c2ab6b8e7 100644
--- a/python/GafferSceneUITest/AttributeInspectorTest.py
+++ b/python/GafferSceneUITest/AttributeInspectorTest.py
@@ -219,7 +219,7 @@ def testSourceAndEdits( self ) :
source = s["light"]["visualiserAttributes"]["scale"],
sourceType = SourceType.Other,
editable=False,
- nonEditableReason = "The target EditScope (editScope1) is not in the scene history."
+ nonEditableReason = "The target edit scope editScope1 is not in the scene history."
)
# If it is in the history though, and we're told to use it, then we will.
@@ -369,7 +369,7 @@ def testSourceAndEdits( self ) :
source = independentAttributeTweakPlug,
sourceType = SourceType.Downstream,
editable = False,
- nonEditableReason = "The target EditScope (editScope2) is disabled."
+ nonEditableReason = "The target edit scope editScope2 is disabled."
)
s["editScope2"]["enabled"].setValue( True )
@@ -455,7 +455,7 @@ def testEditScopeNotInHistory( self ) :
source = light["visualiserAttributes"]["scale"],
sourceType = SourceType.Other,
editable = False,
- nonEditableReason = "The target EditScope (EditScope) is not in the scene history."
+ nonEditableReason = "The target edit scope EditScope is not in the scene history."
)
self.__assertExpectedResult(
@@ -471,7 +471,7 @@ def testEditScopeNotInHistory( self ) :
source = attributeTweaks["tweaks"][0],
sourceType = SourceType.Other,
editable = False,
- nonEditableReason = "The target EditScope (EditScope) is not in the scene history."
+ nonEditableReason = "The target edit scope EditScope is not in the scene history."
)
def testDisabledTweaks( self ) :
@@ -821,6 +821,105 @@ def testLightFilter( self ) :
edit = edit
)
+ def testAcquireEditCreateIfNecessary( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["light"] = GafferSceneTest.TestLight()
+ s["light"]["visualiserAttributes"]["scale"]["enabled"].setValue( True )
+
+ s["group"] = GafferScene.Group()
+ s["editScope"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["light"]["out"] )
+
+ s["editScope"].setup( s["group"]["out"] )
+ s["editScope"]["in"].setInput( s["group"]["out"] )
+
+ inspection = self.__inspect( s["group"]["out"], "/group/light", "gl:visualiser:scale", None )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), s["light"]["visualiserAttributes"]["scale"] )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/group/light", "gl:visualiser:scale", s["editScope"] )
+ self.assertIsNone( inspection.acquireEdit( createIfNecessary = False ) )
+
+ edit = inspection.acquireEdit( createIfNecessary = True )
+ self.assertIsNotNone( edit )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), edit )
+
+ def testDisableEdit( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["light"] = GafferSceneTest.TestLight()
+ s["light"]["visualiserAttributes"]["scale"]["enabled"].setValue( True )
+
+ s["group"] = GafferScene.Group()
+ s["editScope1"] = Gaffer.EditScope()
+ s["editScope2"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["light"]["out"] )
+
+ s["editScope1"].setup( s["group"]["out"] )
+ s["editScope1"]["in"].setInput( s["group"]["out"] )
+
+ s["editScope2"].setup( s["editScope1"]["out"] )
+ s["editScope2"]["in"].setInput( s["editScope1"]["out"] )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["light"]["visualiserAttributes"]["scale"]["enabled"], True )
+ inspection = self.__inspect( s["group"]["out"], "/group/light", "gl:visualiser:scale", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "light.visualiserAttributes.scale.enabled is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : light.visualiserAttributes.scale.enabled is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["light"]["visualiserAttributes"]["scale"]["enabled"], False )
+ Gaffer.MetadataAlgo.setReadOnly( s["light"]["visualiserAttributes"]["scale"], True )
+ inspection = self.__inspect( s["group"]["out"], "/group/light", "gl:visualiser:scale", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "light.visualiserAttributes.scale is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : light.visualiserAttributes.scale is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["light"]["visualiserAttributes"]["scale"], False )
+ inspection = self.__inspect( s["group"]["out"], "/group/light", "gl:visualiser:scale", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( s["light"]["visualiserAttributes"]["scale"]["enabled"].getValue() )
+
+ lightEdit = GafferScene.EditScopeAlgo.acquireAttributeEdit(
+ s["editScope1"], "/group/light", "gl:visualiser:scale", createIfNecessary = True
+ )
+ lightEdit["enabled"].setValue( True )
+ lightEdit["value"].setValue( 2.0 )
+
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "The target edit scope editScope2 is not in the scene history." )
+
+ inspection = self.__inspect( s["editScope2"]["out"], "/group/light", "gl:visualiser:scale", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to editScope1 to disable." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Edit is not in the current edit scope. Change scope to editScope1 to disable.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", s["editScope1"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "editScope1 is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : editScope1 is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], False )
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", s["editScope1"] )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( lightEdit["enabled"].getValue() )
+
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", s["editScope1"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to None to disable." )
+
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "light.visualiserAttributes.scale.enabled is not enabled." )
if __name__ == "__main__" :
unittest.main()
diff --git a/python/GafferSceneUITest/OptionInspectorTest.py b/python/GafferSceneUITest/OptionInspectorTest.py
index 3735046cfc..016a61967e 100644
--- a/python/GafferSceneUITest/OptionInspectorTest.py
+++ b/python/GafferSceneUITest/OptionInspectorTest.py
@@ -162,7 +162,7 @@ def testSourceAndEdits( self ) :
source = s["standardOptions"]["options"]["renderCamera"],
sourceType = SourceType.Other,
editable = False,
- nonEditableReason = "The target EditScope (editScope1) is not in the scene history."
+ nonEditableReason = "The target edit scope editScope1 is not in the scene history."
)
# If it is in the history though, and we're told to use it, then we will.
@@ -285,7 +285,7 @@ def testSourceAndEdits( self ) :
source = s["independentOptions"]["options"]["renderCamera"],
sourceType = SourceType.Downstream,
editable = False,
- nonEditableReason = "The target EditScope (editScope2) is disabled."
+ nonEditableReason = "The target edit scope editScope2 is disabled."
)
s["editScope2"]["enabled"].setValue( True )
@@ -561,7 +561,7 @@ def testRenderPassSourceAndEdits( self ) :
source = s["standardOptions"]["options"]["renderCamera"],
sourceType = SourceType.Other,
editable = False,
- nonEditableReason = "The target EditScope (editScope1) is not in the scene history."
+ nonEditableReason = "The target edit scope editScope1 is not in the scene history."
)
# If it is in the history though, and we're told to use it, then we will.
@@ -684,7 +684,7 @@ def testRenderPassSourceAndEdits( self ) :
source = s["independentOptions"]["options"]["renderCamera"],
sourceType = SourceType.Downstream,
editable = False,
- nonEditableReason = "The target EditScope (editScope2) is disabled."
+ nonEditableReason = "The target edit scope editScope2 is disabled."
)
s["editScope2"]["enabled"].setValue( True )
@@ -892,5 +892,100 @@ def testDefaultValueMetadata( self ) :
edit = edit
)
+ def testAcquireEditCreateIfNecessary( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["standardOptions"] = GafferScene.StandardOptions()
+ s["standardOptions"]["options"]["renderCamera"]["enabled"].setValue( True )
+ s["standardOptions"]["options"]["renderCamera"]["value"].setValue( "/defaultCamera" )
+
+ s["group"] = GafferScene.Group()
+ s["editScope"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["standardOptions"]["out"] )
+
+ s["editScope"].setup( s["group"]["out"] )
+ s["editScope"]["in"].setInput( s["group"]["out"] )
+
+ inspection = self.__inspect( s["group"]["out"], "render:camera", None )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), s["standardOptions"]["options"]["renderCamera"] )
+
+ inspection = self.__inspect( s["editScope"]["out"], "render:camera", s["editScope"] )
+ self.assertIsNone( inspection.acquireEdit( createIfNecessary = False ) )
+
+ edit = inspection.acquireEdit( createIfNecessary = True )
+ self.assertIsNotNone( edit )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), edit )
+
+ def testDisableEdit( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["standardOptions"] = GafferScene.StandardOptions()
+ s["standardOptions"]["options"]["renderCamera"]["enabled"].setValue( True )
+ s["standardOptions"]["options"]["renderCamera"]["value"].setValue( "/defaultCamera" )
+
+ s["group"] = GafferScene.Group()
+ s["editScope1"] = Gaffer.EditScope()
+ s["editScope2"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["standardOptions"]["out"] )
+
+ s["editScope1"].setup( s["group"]["out"] )
+ s["editScope1"]["in"].setInput( s["group"]["out"] )
+
+ s["editScope2"].setup( s["editScope1"]["out"] )
+ s["editScope2"]["in"].setInput( s["editScope1"]["out"] )
+
+ inspection = self.__inspect( s["editScope2"]["out"], "render:camera", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to None to disable." )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["standardOptions"]["options"], True )
+ inspection = self.__inspect( s["group"]["out"], "render:camera", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "standardOptions.options is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : standardOptions.options is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["standardOptions"]["options"], False )
+ inspection = self.__inspect( s["group"]["out"], "render:camera", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( s["standardOptions"]["options"]["renderCamera"]["enabled"].getValue() )
+
+ inspection = self.__inspect( s["editScope2"]["out"], "render:camera", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "No editable source found in history." )
+
+ cameraEdit = GafferScene.EditScopeAlgo.acquireOptionEdit(
+ s["editScope1"], "render:camera", createIfNecessary = True
+ )
+ cameraEdit["enabled"].setValue( True )
+ cameraEdit["value"].setValue( "/bar" )
+
+ inspection = self.__inspect( s["editScope1"]["out"], "render:camera", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "The target edit scope editScope2 is not in the scene history." )
+
+ inspection = self.__inspect( s["editScope2"]["out"], "render:camera", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to editScope1 to disable." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Edit is not in the current edit scope. Change scope to editScope1 to disable.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
+ inspection = self.__inspect( s["editScope1"]["out"], "render:camera", s["editScope1"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "editScope1 is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : editScope1 is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], False )
+ inspection = self.__inspect( s["editScope1"]["out"], "render:camera", s["editScope1"] )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( cameraEdit["enabled"].getValue() )
+
if __name__ == "__main__" :
unittest.main()
diff --git a/python/GafferSceneUITest/ParameterInspectorTest.py b/python/GafferSceneUITest/ParameterInspectorTest.py
index 51fce112d4..f37651b572 100644
--- a/python/GafferSceneUITest/ParameterInspectorTest.py
+++ b/python/GafferSceneUITest/ParameterInspectorTest.py
@@ -156,7 +156,7 @@ def testSourceAndEdits( self ) :
self.__assertExpectedResult(
self.__inspect( s["group"]["out"], "/group/light", "intensity", s["editScope1"] ),
source = s["light"]["parameters"]["intensity"], sourceType = SourceType.Other,
- editable = False, nonEditableReason = "The target EditScope (editScope1) is not in the scene history."
+ editable = False, nonEditableReason = "The target edit scope editScope1 is not in the scene history."
)
# If it is in the history though, and we're told to use it, then we will.
@@ -302,7 +302,7 @@ def testSourceAndEdits( self ) :
self.__assertExpectedResult(
self.__inspect( s["independentLightTweak"]["out"], "/group/light", "intensity", s["editScope2"] ),
source = independentLightTweakPlug, sourceType = SourceType.Downstream,
- editable = False, nonEditableReason = "The target EditScope (editScope2) is disabled."
+ editable = False, nonEditableReason = "The target edit scope editScope2 is disabled."
)
s["editScope2"]["enabled"].setValue( True )
@@ -378,7 +378,7 @@ def testEditScopeNotInHistory( self ) :
self.__assertExpectedResult(
self.__inspect( light["out"], "/light", "exposure", editScope ),
source = light["parameters"]["exposure"], sourceType = SourceType.Other,
- editable = False, nonEditableReason = "The target EditScope (EditScope) is not in the scene history."
+ editable = False, nonEditableReason = "The target edit scope EditScope is not in the scene history."
)
self.__assertExpectedResult(
@@ -390,9 +390,112 @@ def testEditScopeNotInHistory( self ) :
self.__assertExpectedResult(
self.__inspect( shaderTweaks["out"], "/light", "exposure", editScope ),
source = shaderTweaks["tweaks"][0], sourceType = SourceType.Other,
- editable = False, nonEditableReason = "The target EditScope (EditScope) is not in the scene history."
+ editable = False, nonEditableReason = "The target edit scope EditScope is not in the scene history."
)
+ def testAcquireEditCreateIfNecessary( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["light"] = GafferSceneTest.TestLight()
+ s["group"] = GafferScene.Group()
+ s["editScope"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["light"]["out"] )
+
+ s["editScope"].setup( s["group"]["out"] )
+ s["editScope"]["in"].setInput( s["group"]["out"] )
+
+ inspection = self.__inspect( s["group"]["out"], "/group/light", "exposure", None )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), s["light"]["parameters"]["exposure"] )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/group/light", "exposure", s["editScope"] )
+ self.assertIsNone( inspection.acquireEdit( createIfNecessary = False ) )
+
+ edit = inspection.acquireEdit( createIfNecessary = True )
+ self.assertIsNotNone( edit )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), edit )
+
+ def testDisableEdit( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["light"] = GafferSceneTest.TestLight()
+
+ s["lightFilter"] = GafferScene.PathFilter()
+ s["lightFilter"]["paths"].setValue( IECore.StringVectorData( [ "/light" ] ) )
+
+ s["shaderTweaks"] = GafferScene.ShaderTweaks()
+ s["shaderTweaks"]["in"].setInput( s["light"]["out"] )
+ s["shaderTweaks"]["filter"].setInput( s["lightFilter"]["out"] )
+ exposureTweak = Gaffer.TweakPlug( "exposure", 10 )
+ s["shaderTweaks"]["tweaks"].addChild( exposureTweak )
+
+ s["editScope"] = Gaffer.EditScope()
+ s["editScope"].setup( s["shaderTweaks"]["out"] )
+ s["editScope"]["in"].setInput( s["shaderTweaks"]["out"] )
+
+ s["editScope2"] = Gaffer.EditScope()
+ s["editScope2"].setup( s["editScope"]["out"] )
+ s["editScope2"]["in"].setInput( s["editScope"]["out"] )
+
+ Gaffer.MetadataAlgo.setReadOnly( exposureTweak["enabled"], True )
+ inspection = self.__inspect( s["shaderTweaks"]["out"], "/light", "exposure", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "shaderTweaks.tweaks.tweak.enabled is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : shaderTweaks.tweaks.tweak.enabled is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( exposureTweak["enabled"], False )
+ Gaffer.MetadataAlgo.setReadOnly( exposureTweak, True )
+ inspection = self.__inspect( s["shaderTweaks"]["out"], "/light", "exposure", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "shaderTweaks.tweaks.tweak is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : shaderTweaks.tweaks.tweak is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( exposureTweak, False )
+ inspection = self.__inspect( s["shaderTweaks"]["out"], "/light", "exposure", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( exposureTweak["enabled"].getValue() )
+
+ lightEdit = GafferScene.EditScopeAlgo.acquireParameterEdit(
+ s["editScope"], "/light", "light", ( "", "exposure" ), createIfNecessary = True
+ )
+ lightEdit["enabled"].setValue( True )
+ lightEdit["value"].setValue( 2.0 )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "The target edit scope editScope2 is not in the scene history." )
+
+ inspection = self.__inspect( s["editScope2"]["out"], "/light", "exposure", s["editScope2"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to editScope to disable." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Edit is not in the current edit scope. Change scope to editScope to disable.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope"], True )
+ inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", s["editScope"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "editScope is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : editScope is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope"], False )
+ inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", s["editScope"] )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+ inspection.disableEdit()
+ self.assertFalse( lightEdit["enabled"].getValue() )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", s["editScope"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Edit is not in the current edit scope. Change scope to None to disable." )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", None )
+ self.assertEqual( inspection.source(), s["light"]["parameters"]["exposure"] )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "Disabling edits not supported for this plug." )
+
def testDisabledTweaks( self ) :
light = GafferSceneTest.TestLight()
diff --git a/python/GafferSceneUITest/SetMembershipInspectorTest.py b/python/GafferSceneUITest/SetMembershipInspectorTest.py
index 9f4f3070af..d16f991cf2 100644
--- a/python/GafferSceneUITest/SetMembershipInspectorTest.py
+++ b/python/GafferSceneUITest/SetMembershipInspectorTest.py
@@ -192,7 +192,7 @@ def testSourceAndEdits( self ) :
source = s["plane"]["sets"],
sourceType = SourceType.Other,
editable = False,
- nonEditableReason = "The target EditScope (editScope1) is not in the scene history."
+ nonEditableReason = "The target edit scope editScope1 is not in the scene history."
)
# If it is in the history though, and we're told to use it, then we will.
@@ -329,7 +329,7 @@ def testSourceAndEdits( self ) :
source = s["independentSet"]["name"],
sourceType = SourceType.Downstream,
editable = False,
- nonEditableReason = "The target EditScope (editScope2) is disabled."
+ nonEditableReason = "The target edit scope editScope2 is disabled."
)
s["editScope2"]["enabled"].setValue( True )
@@ -594,6 +594,100 @@ def testSetNodeEditSetMembership( self ) :
self.assertFalse( inspector.editSetMembership( inspection, "/plane", GafferScene.EditScopeAlgo.SetMembership.Removed ) )
+ def testAcquireEditCreateIfNecessary( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["plane"] = GafferScene.Plane()
+ s["plane"]["sets"].setValue( "planeSetA planeSetB" )
+
+ s["group"] = GafferScene.Group()
+ s["editScope"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["plane"]["out"] )
+ s["editScope"].setup( s["group"]["out"] )
+ s["editScope"]["in"].setInput( s["group"]["out"] )
+
+ inspection = self.__inspect( s["group"]["out"], "/group/plane", "planeSetA", None )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), s["plane"]["sets"] )
+
+ inspection = self.__inspect( s["editScope"]["out"], "/group/plane", "planeSetA", s["editScope"] )
+ self.assertIsNone( inspection.acquireEdit( createIfNecessary = False ) )
+
+ edit = inspection.acquireEdit( createIfNecessary = True )
+ self.assertIsNotNone( edit )
+ self.assertEqual( inspection.acquireEdit( createIfNecessary = False ), edit )
+
+ def testDisableEdit( self ) :
+
+ s = Gaffer.ScriptNode()
+
+ s["plane"] = GafferScene.Plane()
+ s["plane"]["sets"].setValue( "planeSetA planeSetB" )
+
+ s["group"] = GafferScene.Group()
+
+ s["editScope1"] = Gaffer.EditScope()
+
+ s["group"]["in"][0].setInput( s["plane"]["out"] )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["plane"]["sets"], True )
+ inspection = self.__inspect( s["group"]["out"], "/group/plane", "planeSetA", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "plane.sets is locked." )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["plane"]["sets"], False )
+ inspection = self.__inspect( s["group"]["out"], "/group/plane", "planeSetA", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+
+ inspection.disableEdit()
+ self.assertEqual( s["plane"]["sets"].getValue(), "planeSetB" )
+
+ inspection = self.__inspect( s["group"]["out"], "/group/plane", "planeSetB", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+
+ inspection.disableEdit()
+ self.assertEqual( s["plane"]["sets"].getValue(), "" )
+
+ inspection = self.__inspect( s["group"]["out"], "/group/plane", "planeSetB", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "plane.sets has no edit to disable." )
+
+ s["editScope1"].setup( s["group"]["out"] )
+ s["editScope1"]["in"].setInput( s["group"]["out"] )
+
+ for membership in ( GafferScene.EditScopeAlgo.SetMembership.Added, GafferScene.EditScopeAlgo.SetMembership.Removed ) :
+
+ GafferScene.EditScopeAlgo.setSetMembership(
+ s["editScope1"],
+ IECore.PathMatcher( [ "group/plane" ] ),
+ "planeSetEditScope",
+ membership
+ )
+
+ self.assertEqual(
+ GafferScene.EditScopeAlgo.getSetMembership( s["editScope1"], "/group/plane", "planeSetEditScope"),
+ membership
+ )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", None )
+ self.assertFalse( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "editScope1 is locked." )
+ self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : editScope1 is locked.", inspection.disableEdit )
+
+ Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], False )
+ inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", None )
+ self.assertTrue( inspection.canDisableEdit() )
+ self.assertEqual( inspection.nonDisableableReason(), "" )
+
+ inspection.disableEdit()
+ self.assertEqual(
+ GafferScene.EditScopeAlgo.getSetMembership( s["editScope1"], "/group/plane", "planeSetEditScope"),
+ GafferScene.EditScopeAlgo.SetMembership.Unchanged
+ )
if __name__ == "__main__" :
unittest.main()
\ No newline at end of file
diff --git a/python/GafferUI/Menu.py b/python/GafferUI/Menu.py
index d34d8df71c..adf001fa36 100644
--- a/python/GafferUI/Menu.py
+++ b/python/GafferUI/Menu.py
@@ -728,6 +728,9 @@ def event( self, qEvent ) :
if action and action.statusTip() :
QtWidgets.QToolTip.showText( qEvent.globalPos(), action.statusTip(), self )
return True
+ elif QtWidgets.QToolTip.isVisible() :
+ QtWidgets.QToolTip.hideText()
+ return True
return QtWidgets.QMenu.event( self, qEvent )
diff --git a/src/GafferSceneUI/AttributeInspector.cpp b/src/GafferSceneUI/AttributeInspector.cpp
index eb745ebfd5..12e9b6873c 100644
--- a/src/GafferSceneUI/AttributeInspector.cpp
+++ b/src/GafferSceneUI/AttributeInspector.cpp
@@ -394,12 +394,13 @@ Inspector::EditFunctionOrFailure AttributeInspector::editFunction( Gaffer::EditS
editScope = EditScopePtr( editScope ),
attributeName,
context = history->context
- ] () {
+ ] ( bool createIfNecessary ) {
Context::Scope scope( context.get() );
return EditScopeAlgo::acquireAttributeEdit(
editScope.get(),
context->get( ScenePlug::scenePathContextName ),
- attributeName
+ attributeName,
+ createIfNecessary
);
};
}
diff --git a/src/GafferSceneUI/Inspector.cpp b/src/GafferSceneUI/Inspector.cpp
index 6cec443f42..8ce8c28f61 100644
--- a/src/GafferSceneUI/Inspector.cpp
+++ b/src/GafferSceneUI/Inspector.cpp
@@ -38,6 +38,7 @@
#include "Gaffer/Animation.h"
#include "Gaffer/MetadataAlgo.h"
+#include "Gaffer/OptionalValuePlug.h"
#include "Gaffer/PathFilter.h"
#include "Gaffer/ScriptNode.h"
#include "Gaffer/Spreadsheet.h"
@@ -211,18 +212,23 @@ Inspector::ResultPtr Inspector::inspect() const
if( result->editScope() && !result->m_editScopeInHistory )
{
- result->m_editFunction = fmt::format(
- "The target EditScope ({}) is not in the scene history.",
+ const std::string nonEditableReason = fmt::format(
+ "The target edit scope {} is not in the scene history.",
result->editScope()->relativeName( result->editScope()->scriptNode() )
);
+ result->m_editFunction = nonEditableReason;
+ result->m_disableEditFunction = nonEditableReason;
result->m_sourceType = Result::SourceType::Other;
}
-
- else if( !result->m_source && !result->editable() )
+ else if( !result->m_source )
{
- // There's no source plug and no way of making
- // the property.
- result->m_editFunction = "No editable source found in history.";
+ if( !result->editable() )
+ {
+ // There's no source plug and no way of making
+ // the property.
+ result->m_editFunction = "No editable source found in history.";
+ }
+ result->m_disableEditFunction = "No editable source found in history.";
}
if( fallbackValue )
@@ -289,14 +295,27 @@ void Inspector::inspectHistoryWalk( const GafferScene::SceneAlgo::History *histo
if( nonEditableReason.empty() )
{
- result->m_editFunction = [source = result->m_source] () { return source; };
+ result->m_editFunction = [source = result->m_source] ( bool unused ) { return source; };
+ result->m_disableEditFunction = disableEditFunction( result->m_source.get(), history );
result->m_editWarning = editWarning;
}
else
{
result->m_editFunction = nonEditableReason;
+ result->m_disableEditFunction = nonEditableReason;
}
}
+ else if( node->ancestor() && node->ancestor() != result->m_editScope )
+ {
+ result->m_disableEditFunction = fmt::format(
+ "Edit is not in the current edit scope. Change scope to {} to disable.",
+ node->ancestor()->relativeName( node->ancestor()->scriptNode() )
+ );
+ }
+ else if( !node->ancestor() && result->m_editScope )
+ {
+ result->m_disableEditFunction = "Edit is not in the current edit scope. Change scope to None to disable.";
+ }
}
}
}
@@ -338,7 +357,7 @@ void Inspector::inspectHistoryWalk( const GafferScene::SceneAlgo::History *histo
else
{
result->m_editFunction = fmt::format(
- "The target EditScope ({}) is disabled.",
+ "The target edit scope {} is disabled.",
editScope->relativeName( editScope->scriptNode() )
);
}
@@ -368,6 +387,47 @@ Inspector::EditFunctionOrFailure Inspector::editFunction( Gaffer::EditScope *edi
return "Editing not supported";
}
+Inspector::DisableEditFunctionOrFailure Inspector::disableEditFunction( Gaffer::ValuePlug *plug, const GafferScene::SceneAlgo::History *history ) const
+{
+ Gaffer::BoolPlugPtr enabledPlug;
+ if( auto tweakPlug = runTimeCast( plug ) )
+ {
+ enabledPlug = tweakPlug->enabledPlug();
+ }
+ else if( auto nameValuePlug = runTimeCast( plug ) )
+ {
+ enabledPlug = nameValuePlug->enabledPlug();
+ }
+ else if( auto optionalValuePlug = runTimeCast( plug ) )
+ {
+ enabledPlug = optionalValuePlug->enabledPlug();
+ }
+
+ if( enabledPlug )
+ {
+ if( const GraphComponent *readOnlyReason = MetadataAlgo::readOnlyReason( enabledPlug.get() ) )
+ {
+ return fmt::format( "{} is locked.", readOnlyReason->relativeName( readOnlyReason->ancestor() ) );
+ }
+ else if( !enabledPlug->settable() )
+ {
+ return fmt::format( "{} is not settable.", enabledPlug->relativeName( enabledPlug->ancestor() ) );
+ }
+ else if( !enabledPlug->getValue() )
+ {
+ return fmt::format( "{} is not enabled.", enabledPlug->relativeName( enabledPlug->ancestor() ) );
+ }
+ else
+ {
+ return [ enabledPlug ] () { enabledPlug->setValue( false ); };
+ }
+ }
+ else
+ {
+ return "Disabling edits not supported for this plug.";
+ }
+}
+
IECore::ConstObjectPtr Inspector::fallbackValue( const GafferScene::SceneAlgo::History *history, std::string &description ) const
{
return nullptr;
@@ -666,16 +726,41 @@ std::string Inspector::Result::nonEditableReason() const
return "";
}
-Gaffer::ValuePlugPtr Inspector::Result::acquireEdit() const
+Gaffer::ValuePlugPtr Inspector::Result::acquireEdit( bool createIfNecessary ) const
{
if( m_editFunction.which() == 0 )
{
- return boost::get( m_editFunction )();
+ return boost::get( m_editFunction )( createIfNecessary );
}
throw IECore::Exception( "Not editable : " + boost::get( m_editFunction ) );
}
+bool Inspector::Result::canDisableEdit() const
+{
+ return m_disableEditFunction.which() == 0 && boost::get( m_disableEditFunction ) != nullptr;
+}
+
+std::string Inspector::Result::nonDisableableReason() const
+{
+ if( m_disableEditFunction.which() == 1 )
+ {
+ return boost::get( m_disableEditFunction );
+ }
+
+ return "";
+}
+
+void Inspector::Result::disableEdit() const
+{
+ if( m_disableEditFunction.which() == 0 )
+ {
+ return boost::get( m_disableEditFunction )();
+ }
+
+ throw IECore::Exception( "Cannot disable edit : " + boost::get( m_disableEditFunction ) );
+}
+
std::string Inspector::Result::editWarning() const
{
return m_editWarning;
diff --git a/src/GafferSceneUI/OptionInspector.cpp b/src/GafferSceneUI/OptionInspector.cpp
index 71e152f362..6ed624fa19 100644
--- a/src/GafferSceneUI/OptionInspector.cpp
+++ b/src/GafferSceneUI/OptionInspector.cpp
@@ -293,12 +293,13 @@ Inspector::EditFunctionOrFailure OptionInspector::editFunction( Gaffer::EditScop
renderPass,
option = m_option,
context = history->context
- ] () {
+ ] ( bool createIfNecessary ) {
Context::Scope scope( context.get() );
return EditScopeAlgo::acquireRenderPassOptionEdit(
editScope.get(),
renderPass,
- option
+ option,
+ createIfNecessary
);
};
}
@@ -326,11 +327,12 @@ Inspector::EditFunctionOrFailure OptionInspector::editFunction( Gaffer::EditScop
editScope = EditScopePtr( editScope ),
option = m_option,
context = history->context
- ] () {
+ ] ( bool createIfNecessary ) {
Context::Scope scope( context.get() );
return EditScopeAlgo::acquireOptionEdit(
editScope.get(),
- option
+ option,
+ createIfNecessary
);
};
}
diff --git a/src/GafferSceneUI/ParameterInspector.cpp b/src/GafferSceneUI/ParameterInspector.cpp
index 305e5b49ac..ab54e4da18 100644
--- a/src/GafferSceneUI/ParameterInspector.cpp
+++ b/src/GafferSceneUI/ParameterInspector.cpp
@@ -215,13 +215,14 @@ Inspector::EditFunctionOrFailure ParameterInspector::editFunction( Gaffer::EditS
attributeName = attributeHistory->attributeName,
context = attributeHistory->context,
parameter = m_parameter
- ] () {
+ ] ( bool createIfNecessary ) {
Context::Scope scope( context.get() );
return EditScopeAlgo::acquireParameterEdit(
editScope.get(),
context->get( ScenePlug::scenePathContextName ),
attributeName,
- parameter
+ parameter,
+ createIfNecessary
);
};
}
diff --git a/src/GafferSceneUI/SetMembershipInspector.cpp b/src/GafferSceneUI/SetMembershipInspector.cpp
index bbf5be0616..5041930888 100644
--- a/src/GafferSceneUI/SetMembershipInspector.cpp
+++ b/src/GafferSceneUI/SetMembershipInspector.cpp
@@ -127,29 +127,8 @@ HistoryCache g_historyCache(
);
-} // namespace
-
-SetMembershipInspector::SetMembershipInspector(
- const GafferScene::ScenePlugPtr &scene,
- const Gaffer::PlugPtr &editScope,
- IECore::InternedString setName
-) :
-Inspector( "setMembership", setName.string(), editScope ),
-m_scene( scene ),
-m_setName( setName )
+bool editSetMembership( Gaffer::Plug *plug, const std::string &setName, const ScenePlug::ScenePath &path, EditScopeAlgo::SetMembership setMembership )
{
- m_scene->node()->plugDirtiedSignal().connect(
- boost::bind( &SetMembershipInspector::plugDirtied, this, ::_1 )
- );
-
- Metadata::plugValueChangedSignal().connect( boost::bind( &SetMembershipInspector::plugMetadataChanged, this, ::_3, ::_4 ) );
- Metadata::nodeValueChangedSignal().connect( boost::bind( &SetMembershipInspector::nodeMetadataChanged, this, ::_2, ::_3 ) );
-}
-
-bool SetMembershipInspector::editSetMembership( const Result *inspection, const ScenePlug::ScenePath &path, EditScopeAlgo::SetMembership setMembership ) const
-{
- PlugPtr plug = inspection->acquireEdit();
-
if( auto objectNode = runTimeCast( plug->node() ) )
{
std::vector sets;
@@ -157,14 +136,14 @@ bool SetMembershipInspector::editSetMembership( const Result *inspection, const
if( setMembership == EditScopeAlgo::SetMembership::Added )
{
- if( std::find( sets.begin(), sets.end(), m_setName.string() ) == sets.end() )
+ if( std::find( sets.begin(), sets.end(), setName ) == sets.end() )
{
- sets.push_back( m_setName.string() );
+ sets.push_back( setName );
}
}
else
{
- sets.erase( std::remove( sets.begin(), sets.end(), m_setName.string() ), sets.end() );
+ sets.erase( std::remove( sets.begin(), sets.end(), setName ), sets.end() );
}
objectNode->setsPlug()->setValue( boost::algorithm::join( sets, " " ) );
@@ -183,7 +162,7 @@ bool SetMembershipInspector::editSetMembership( const Result *inspection, const
EditScopeAlgo::setSetMembership(
editScope,
m,
- m_setName.string(),
+ setName,
setMembership
);
@@ -194,6 +173,49 @@ bool SetMembershipInspector::editSetMembership( const Result *inspection, const
return false;
}
+std::string nonDisableableReason( const Gaffer::Plug *plug, const std::string &setName )
+{
+ if( const GraphComponent *readOnlyReason = MetadataAlgo::readOnlyReason( plug ) )
+ {
+ return fmt::format( "{} is locked.", readOnlyReason->relativeName( readOnlyReason->ancestor() ) );
+ }
+ else if( auto objectNode = runTimeCast( plug->node() ) )
+ {
+ std::vector sets;
+ IECore::StringAlgo::tokenize( objectNode->setsPlug()->getValue(), ' ', sets );
+ if( std::find( sets.begin(), sets.end(), setName ) == sets.end() )
+ {
+ return fmt::format( "{} has no edit to disable.", plug->relativeName( plug->ancestor() ) );
+ }
+ }
+
+ return "";
+}
+
+} // namespace
+
+SetMembershipInspector::SetMembershipInspector(
+ const GafferScene::ScenePlugPtr &scene,
+ const Gaffer::PlugPtr &editScope,
+ IECore::InternedString setName
+) :
+Inspector( "setMembership", setName.string(), editScope ),
+m_scene( scene ),
+m_setName( setName )
+{
+ m_scene->node()->plugDirtiedSignal().connect(
+ boost::bind( &SetMembershipInspector::plugDirtied, this, ::_1 )
+ );
+
+ Metadata::plugValueChangedSignal().connect( boost::bind( &SetMembershipInspector::plugMetadataChanged, this, ::_3, ::_4 ) );
+ Metadata::nodeValueChangedSignal().connect( boost::bind( &SetMembershipInspector::nodeMetadataChanged, this, ::_2, ::_3 ) );
+}
+
+bool SetMembershipInspector::editSetMembership( const Result *inspection, const ScenePlug::ScenePath &path, EditScopeAlgo::SetMembership setMembership ) const
+{
+ return ::editSetMembership( inspection->acquireEdit().get(), m_setName.string(), path, setMembership );
+}
+
GafferScene::SceneAlgo::History::ConstPtr SetMembershipInspector::history() const
{
if( !m_scene->existsPlug()->getValue() )
@@ -312,9 +334,29 @@ Inspector::EditFunctionOrFailure SetMembershipInspector::editFunction( Gaffer::E
editScope = editScope,
setName,
context = history->context
- ] () {
+ ] ( bool createIfNecessary ) {
Context::Scope scope( context.get() );
- return EditScopeAlgo::acquireSetEdits( editScope, setName );
+ return EditScopeAlgo::acquireSetEdits( editScope, setName, createIfNecessary );
+ };
+ }
+}
+
+Inspector::DisableEditFunctionOrFailure SetMembershipInspector::disableEditFunction( Gaffer::ValuePlug *plug, const GafferScene::SceneAlgo::History *history ) const
+{
+ const std::string nonDisableableReason = ::nonDisableableReason( plug, m_setName );
+
+ if( !nonDisableableReason.empty() )
+ {
+ return nonDisableableReason;
+ }
+ else
+ {
+ return [
+ plug = PlugPtr( plug ),
+ setName = m_setName,
+ path = history->context->get( ScenePlug::scenePathContextName )
+ ] () {
+ return ::editSetMembership( plug.get(), setName.string(), path, EditScopeAlgo::SetMembership::Unchanged );
};
}
}
diff --git a/src/GafferSceneUIModule/InspectorBinding.cpp b/src/GafferSceneUIModule/InspectorBinding.cpp
index 5bba05dd2e..23d4ff2078 100644
--- a/src/GafferSceneUIModule/InspectorBinding.cpp
+++ b/src/GafferSceneUIModule/InspectorBinding.cpp
@@ -72,10 +72,16 @@ IECore::ObjectPtr valueWrapper( const GafferSceneUI::Private::Inspector::Result
return result.value() ? result.value()->copy() : nullptr;
}
-Gaffer::ValuePlugPtr acquireEditWrapper( GafferSceneUI::Private::Inspector::Result &result )
+Gaffer::ValuePlugPtr acquireEditWrapper( GafferSceneUI::Private::Inspector::Result &result, bool createIfNecessary )
{
ScopedGILRelease gilRelease;
- return result.acquireEdit();
+ return result.acquireEdit( createIfNecessary );
+}
+
+void disableEditWrapper( GafferSceneUI::Private::Inspector::Result &result )
+{
+ ScopedGILRelease gilRelease;
+ return result.disableEdit();
}
bool editSetMembershipWrapper(
@@ -133,8 +139,11 @@ void GafferSceneUIModule::bindInspector()
.def( "fallbackDescription", &Inspector::Result::fallbackDescription, return_value_policy() )
.def( "editable", &Inspector::Result::editable )
.def( "nonEditableReason", &Inspector::Result::nonEditableReason )
- .def( "acquireEdit", &acquireEditWrapper )
+ .def( "acquireEdit", &acquireEditWrapper, ( arg( "createIfNecessary" ) = true ) )
.def( "editWarning", &Inspector::Result::editWarning )
+ .def( "canDisableEdit", &Inspector::Result::canDisableEdit )
+ .def( "nonDisableableReason", &Inspector::Result::nonDisableableReason )
+ .def( "disableEdit", &disableEditWrapper )
;
enum_( "SourceType" )