Skip to content

Commit

Permalink
Inspector : Make handling of downstream overrides consistent
Browse files Browse the repository at this point in the history
We were already allowing edits in a nominated EditScope even when there was a downstream override that would prevent you from seeing the result. This was communicated in the UI by an orange "overridden" background colour in the inspector cell, and a warning message in the editing popup window. But in "Source" mode we made no such allowance, meaning that you couldn't make an edit at source if it was overridden downstream, and the UI didn't even show you that it was overridden downstream. There was no background colour and a spurious message about the target not being in the history.

We now treat "Source" mode the same as EditScope mode, allowing edits at source even when overridden downstream. Along with that the UI now also shows the overridden status in the background colour and shows the appropriate warning when editing.

This commit bundles a couple of other things which might ideally have been separate commits, but which arose naturally while figuring out the implementation :

- Consolidated some member data into a single `Result::m_editors` struct. This encourages everything to be initialised together, making the data flow a little clearer, and using `std::optional` to make it unambiguous whether we have initialised yet or not.
- Adjusted the failure messages when an attempt is made to disabled a non-existent edit. Instead of directing the user to change edit scope and disabled a _different_ edit, we now just explain that there is no edit to disable.
  • Loading branch information
johnhaddon committed Dec 10, 2024
1 parent 8034c18 commit a4a1107
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 154 deletions.
5 changes: 4 additions & 1 deletion Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ Fixes
- Widget : Fixed `event.sourceWidget` for DragDropEvents generated from a Qt native drag within the same Gaffer process. This will now reference the `GafferUI.Widget` that the Qt source widget belongs to, if any.
- Catalogue : Fixed bug which "stole" drags that crossed the image listing but which were destined elsewhere, for instance a drag from the HierarchyView to a PathFilter in the GraphEditor.
- GadgetWidget : Fixed signal handling bug in `setViewportGadget()`. This could cause the widget to attempt to redraw unnecessarily when the _old_ viewport requested a redraw.
- AttributeEditor, LightEditor, RenderPassEditor : Fixed warning message which referred to "None" rather than the "Source" scope.
- AttributeEditor, LightEditor, RenderPassEditor :
- Fixed bugs which prevented edits being made in "Source" scope when there was a downstream edit in an EditScope.
- Fixed warning messages when attempting to disable a non-existent edit.
- Fixed warning message which referred to "None" rather than the "Source" scope.

API
---
Expand Down
11 changes: 8 additions & 3 deletions include/GafferSceneUI/Private/Inspector.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,15 @@ class GAFFERSCENEUI_API Inspector::Result : public IECore::RefCounted
Gaffer::EditScopePtr m_editScope;
bool m_editScopeInHistory;

EditFunctionOrFailure m_editFunction;
std::string m_editWarning;
struct Editors
{
/// \todo Rename to `acquireEditFunction`?
EditFunctionOrFailure editFunction;
std::string editWarning;
DisableEditFunctionOrFailure disableEditFunction;
};

DisableEditFunctionOrFailure m_disableEditFunction;
std::optional<Editors> m_editors;

};

Expand Down
6 changes: 3 additions & 3 deletions python/GafferSceneUITest/AttributeInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,8 +912,8 @@ def testDisableEdit( self ) :

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 )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope2." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : There is no edit in editScope2.", inspection.disableEdit )

Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", s["editScope1"] )
Expand All @@ -930,7 +930,7 @@ def testDisableEdit( self ) :

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." )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope1." )

inspection = self.__inspect( s["editScope1"]["out"], "/group/light", "gl:visualiser:scale", None )
self.assertFalse( inspection.canDisableEdit() )
Expand Down
14 changes: 8 additions & 6 deletions python/GafferSceneUITest/OptionInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,13 +652,15 @@ def testRenderPassSourceAndEdits( self ) :
)

# When using no scope, make sure that we don't inadvertently edit the contents of an EditScope.
# We should be allowed to edit the source before the scope, but only with a warning about there
# being a downstream override.

self.__assertExpectedResult(
self.__inspect( s["editScope2"]["out"], "render:camera", None, context ),
source = s["editScope1"]["standardOptions3"]["options"]["renderCamera"],
sourceType = SourceType.Other,
editable = False,
nonEditableReason = "Source is in an EditScope. Change scope to editScope1 to edit."
sourceType = SourceType.Downstream,
editable = True,
editWarning = "Option has edits downstream in editScope1."
)

# If there is a StandardOptions node outside of an edit scope, make sure we use that with no scope
Expand Down Expand Up @@ -950,7 +952,7 @@ def testDisableEdit( self ) :

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." )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope2." )

Gaffer.MetadataAlgo.setReadOnly( s["standardOptions"]["options"], True )
inspection = self.__inspect( s["group"]["out"], "render:camera", None )
Expand Down Expand Up @@ -986,8 +988,8 @@ def testDisableEdit( self ) :

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 )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope2." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : There is no edit in editScope2.", inspection.disableEdit )

Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
inspection = self.__inspect( s["editScope1"]["out"], "render:camera", s["editScope1"] )
Expand Down
77 changes: 72 additions & 5 deletions python/GafferSceneUITest/ParameterInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,14 +478,15 @@ def testDisableEdit( self ) :
self.assertEqual( inspection.nonDisableableReason(), "The target edit scope editScope2 is not in the scene history." )

inspection = self.__inspect( s["editScope2"]["out"], "/light", "exposure", None )
self.assertTrue( inspection.acquireEdit( False ).isSame( s["light"]["parameters"]["exposure"] ) )
self.assertFalse( inspection.canDisableEdit() )
self.assertEqual( inspection.nonDisableableReason(), "Source is in an EditScope. Change scope to editScope to disable." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Source is in an EditScope. Change scope to editScope to disable.", inspection.disableEdit )
self.assertEqual( inspection.nonDisableableReason(), "Disabling edits not supported for this plug." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Disabling edits not supported for this plug.", inspection.disableEdit )

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 )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope2." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : There is no edit in editScope2.", inspection.disableEdit )

Gaffer.MetadataAlgo.setReadOnly( s["editScope"], True )
inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", s["editScope"] )
Expand All @@ -502,7 +503,7 @@ def testDisableEdit( self ) :

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 Source to disable." )
self.assertEqual( inspection.nonDisableableReason(), "There is no edit in editScope." )

inspection = self.__inspect( s["editScope"]["out"], "/light", "exposure", None )
self.assertEqual( inspection.source(), s["light"]["parameters"]["exposure"] )
Expand Down Expand Up @@ -1088,6 +1089,72 @@ def testShaderNetwork( self ) :
editWarning = "Edits to box.add may affect other locations in the scene."
)

def testLightCreatedInEditScope( self ) :

light = GafferSceneTest.TestLight()

editScope1 = Gaffer.EditScope( "EditScope1" )
editScope1.setup( light["out"] )

editScope1["light"] = light
editScope1["parent"] = GafferScene.Parent()
editScope1["parent"]["parent"].setValue( "/" )
editScope1["parent"]["in"].setInput( editScope1["BoxIn"]["out"] )
editScope1["parent"]["children"][0].setInput( editScope1["light"]["out"] )

editScope1["BoxOut"]["in"].setInput( editScope1["parent"]["out"] )

editScope2 = Gaffer.EditScope( "EditScope2" )
editScope2.setup( editScope1["out"] )
editScope2["in"].setInput( editScope1["out"] )

# Make edit in EditScope2.

i = self.__inspect( editScope2["out"], "/light", "exposure", editScope2 )
scope2Edit = i.acquireEdit()
self.assertTrue( editScope2.isAncestorOf( scope2Edit ) )
scope2Edit["enabled"].setValue( True )
scope2Edit["value"].setValue( 2 )

# Check that we can still edit in EditScope1, accompanied by
# a suitable warning.

self.__assertExpectedResult(
self.__inspect( editScope2["out"], "/light", "exposure", editScope1 ),
source = scope2Edit,
sourceType = GafferSceneUI.Private.Inspector.Result.SourceType.Downstream,
editable = True,
edit = light["parameters"]["exposure"],
editWarning = "Parameter has edits downstream in EditScope2."
)

def testSourceWithDownstreamOverride( self ) :

light = GafferSceneTest.TestLight()

editScope = Gaffer.EditScope()
editScope.setup( light["out"] )
editScope["in"].setInput( light["out"] )

# Make edit in EditScope.

i = self.__inspect( editScope["out"], "/light", "exposure", editScope )
scopeEdit = i.acquireEdit()
self.assertTrue( editScope.isAncestorOf( scopeEdit ) )
scopeEdit["enabled"].setValue( True )
scopeEdit["value"].setValue( 2 )

# Check that we can still edit the source, accompanied by
# a suitable warning.

self.__assertExpectedResult(
self.__inspect( editScope["out"], "/light", "exposure", editScope = None ),
source = scopeEdit,
sourceType = GafferSceneUI.Private.Inspector.Result.SourceType.Downstream,
editable = True,
edit = light["parameters"]["exposure"],
editWarning = "Parameter has edits downstream in EditScope."
)

if __name__ == "__main__":
unittest.main()
7 changes: 4 additions & 3 deletions python/GafferSceneUITest/SetMembershipInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,9 +690,10 @@ def testDisableEdit( self ) :

Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], False )
inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", None )
self.assertTrue( inspection.acquireEdit( False ).isSame( s["plane"]["sets"] ) )
self.assertFalse( inspection.canDisableEdit() )
self.assertEqual( inspection.nonDisableableReason(), "Source is in an EditScope. Change scope to editScope1 to disable." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : Source is in an EditScope. Change scope to editScope1 to disable.", inspection.disableEdit )
self.assertEqual( inspection.nonDisableableReason(), "plane.sets has no edit to disable." )
self.assertRaisesRegex( IECore.Exception, "Cannot disable edit : plane.sets has no edit to disable.", inspection.disableEdit )

inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", s["editScope1"] )
self.assertTrue( inspection.canDisableEdit() )
Expand All @@ -705,4 +706,4 @@ def testDisableEdit( self ) :
)

if __name__ == "__main__" :
unittest.main()
unittest.main()
Loading

0 comments on commit a4a1107

Please sign in to comment.