Skip to content

Commit

Permalink
Inspector, TransformTool : Do not edit EditScopes when target is "None"
Browse files Browse the repository at this point in the history
  • Loading branch information
murraystevenson committed Sep 18, 2024
1 parent a831448 commit cc9cca1
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 19 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Improvements
- 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 <kdb>D</kdb> shortcut now act as a toggle, where edits disabled in the current session via these actions can be reenabled with <kbd>D</kbd> or by selecting "Reenable Edit" from the right-click menu.
- EditScope : Setting a Viewer or Editor's target edit scope to "None" will now prevent edits from being made within any upstream edit scope. To make edits in an edit scope, it must be set as the target.

Fixes
-----
Expand Down
15 changes: 15 additions & 0 deletions python/GafferSceneUITest/AttributeInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,16 @@ def testSourceAndEdits( self ) :
edit = editScopeAttributeTweak
)

# When using no scope, make sure that we don't inadvertently edit the contents of an EditScope.

self.__assertExpectedResult(
self.__inspect( s["editScope2"]["out"], "/light2", "gl:visualiser:scale", None ),
source = editScopeAttributeTweak,
sourceType = SourceType.Other,
editable = False,
nonEditableReason = "Source is in an EditScope. Change scope to editScope1 to edit."
)

# If there is a manual tweak outside of an edit scope, make sure we use that with no scope
s["independentAttributeTweak"] = GafferScene.AttributeTweaks()
s["independentAttributeTweak"]["in"].setInput( s["editScope2"]["out"] )
Expand Down Expand Up @@ -895,6 +905,11 @@ def testDisableEdit( self ) :
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", None )
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 )

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." )
Expand Down
15 changes: 15 additions & 0 deletions python/GafferSceneUITest/OptionInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,16 @@ def testRenderPassSourceAndEdits( self ) :
edit = s["editScope1"]["standardOptions3"]["options"]["renderCamera"]
)

# When using no scope, make sure that we don't inadvertently edit the contents of an EditScope.

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."
)

# If there is a StandardOptions node outside of an edit scope, make sure we use that with no scope

s["independentOptions"] = GafferScene.StandardOptions()
Expand Down Expand Up @@ -969,6 +979,11 @@ def testDisableEdit( self ) :
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", None )
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 )

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." )
Expand Down
13 changes: 13 additions & 0 deletions python/GafferSceneUITest/ParameterInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ def testSourceAndEdits( self ) :
editable = True, edit = editScopeShaderTweak
)

# When using no scope, make sure that we don't inadvertently edit the contents of an EditScope.

self.__assertExpectedResult(
self.__inspect( s["editScope2"]["out"], "/light2", "intensity", None ),
source = editScopeShaderTweak, sourceType = SourceType.Other,
editable = False, nonEditableReason = "Source is in an EditScope. Change scope to editScope1 to edit."
)

# If there is a manual tweak outside of an edit scope make sure we use that with no scope

s["independentLightTweak"] = GafferScene.ShaderTweaks()
Expand Down Expand Up @@ -469,6 +477,11 @@ def testDisableEdit( self ) :
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", None )
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 )

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." )
Expand Down
17 changes: 16 additions & 1 deletion python/GafferSceneUITest/SetMembershipInspectorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,16 @@ def testSourceAndEdits( self ) :
edit = s["editScope1"]["setPlane2"]["name"]
)

# When using no scope, make sure that we don't inadvertently edit the contents of an EditScope.

self.__assertExpectedResult(
self.__inspect( s["editScope2"]["out"], "/plane2", "planeSet", None ),
source = s["editScope1"]["setPlane2"]["name"],
sourceType = SourceType.Other,
editable = False,
nonEditableReason = "Source is in an EditScope. Change scope to editScope1 to edit."
)

# If there is a manual set node outside of an edit scope, make sure we use that with no scope
s["independentSet"] = GafferScene.Set()
s["independentSet"]["name"].setValue( "planeSet" )
Expand Down Expand Up @@ -673,13 +683,18 @@ def testDisableEdit( self ) :
)

Gaffer.MetadataAlgo.setReadOnly( s["editScope1"], True )
inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", None )
inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", 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/plane", "planeSetEditScope", None )
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 )

inspection = self.__inspect( s["editScope1"]["out"], "/group/plane", "planeSetEditScope", s["editScope1"] )
self.assertTrue( inspection.canDisableEdit() )
self.assertEqual( inspection.nonDisableableReason(), "" )

Expand Down
22 changes: 22 additions & 0 deletions python/GafferSceneUITest/TransformToolTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,5 +333,27 @@ def testDontEditUpstreamOfReference( self ) :
self.assertEqual( selection.warning(), "Transform is locked as it is inside \"reference\" which disallows edits to its children" )
self.assertFalse( selection.editable() )

def testDisallowEditingWithEditScopeNone( self ) :

script = Gaffer.ScriptNode()

script["plane"] = GafferScene.Plane()

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

selection = GafferSceneUI.TransformTool.Selection( script["editScope"]["out"], "/plane", script.context(), script["editScope"] )
edit = selection.acquireTransformEdit()
self.assertTrue( script["editScope"].isAncestorOf( edit.translate ) )

selection = GafferSceneUI.TransformTool.Selection( script["editScope"]["out"], "/plane", script.context(), None )
self.assertFalse( selection.editable() )
self.assertEqual( selection.warning(), "Source is in an EditScope. Change scope to editScope to edit" )

selection = GafferSceneUI.TransformTool.Selection( script["editScope"]["out"], "/plane", script.context(), script["editScope"] )
self.assertTrue( selection.editable() )
self.assertEqual( selection.warning(), "" )

if __name__ == "__main__":
unittest.main()
18 changes: 6 additions & 12 deletions python/GafferSceneUITest/TranslateToolTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1043,26 +1043,20 @@ def testTransformInEditScopeButEditScopeOff( self ) :

GafferSceneUI.ScriptNodeAlgo.setSelectedPaths( script, IECore.PathMatcher( [ "/sphere" ] ) )

# We want the TranslateTool to pick up and use that edit
# even if we haven't told it to use that EditScope.
# We don't want the TranslateTool to pick up and use that edit
# as we haven't told it to use that EditScope.

tool = GafferSceneUI.TranslateTool( view )
tool["active"].setValue( True )

self.assertEqual( tool.handlesTransform(), imath.M44f().translate( imath.V3f( 1, 0, 0 ) ) )
self.assertEqual( len( tool.selection() ), 1 )
self.assertTrue( tool.selectionEditable() )
self.assertTrue( tool.selection()[0].editable() )
self.assertEqual( tool.selection()[0].acquireTransformEdit( createIfNecessary = False ), transformEdit )
self.assertFalse( tool.selectionEditable() )
self.assertFalse( tool.selection()[0].editable() )
self.assertEqual(
tool.selection()[0].editTarget(),
transformEdit.translate.ancestor( Gaffer.Spreadsheet.RowPlug )
tool.selection()[0].warning(),
"Source is in an EditScope. Change scope to editScope to edit"
)

tool.translate( imath.V3f( 0, 1, 0 ) )
self.assertEqual( tool.handlesTransform(), imath.M44f().translate( imath.V3f( 1, 1, 0 ) ) )
self.assertEqual( transformEdit.translate.getValue(), imath.V3f( 1, 1, 0 ) )

def testNonEditableSelections( self ) :

script = Gaffer.ScriptNode()
Expand Down
2 changes: 1 addition & 1 deletion python/GafferUI/EditScopeUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def getToolTip( self ) :

editScope = self.__editScope()
if editScope is None :
return "Edits will be made using the last relevant node, including nodes not in any EditScope."
return "Edits will be made using the last relevant node found outside of an edit scope.\n\nTo make an edit in an edit scope, choose it from the menu."

unusableReason = self.__unusableReason( editScope )
if unusableReason :
Expand Down
16 changes: 15 additions & 1 deletion src/GafferSceneUI/Inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,21 @@ void Inspector::inspectHistoryWalk( const GafferScene::SceneAlgo::History *histo

// See if we can use it for editing

if( !result->m_editScope || node->ancestor<EditScope>() == result->m_editScope )
if( !result->m_editScope && node->ancestor<EditScope>() )
{
// We don't allow editing if the user hasn't requested a specific scope
// (they have selected "None" from the Menu) and the upstream edit is
// inside _any_ EditScope.
result->m_editFunction = fmt::format(
"Source is in an EditScope. Change scope to {} to edit.",
node->ancestor<EditScope>()->relativeName( node->ancestor<EditScope>()->scriptNode() )
);
result->m_disableEditFunction = fmt::format(
"Source is in an EditScope. Change scope to {} to disable.",
node->ancestor<EditScope>()->relativeName( node->ancestor<EditScope>()->scriptNode() )
);
}
else if( !result->m_editScope || node->ancestor<EditScope>() == result->m_editScope )
{
const std::string nonEditableReason = ::nonEditableReason( result->m_source.get() );

Expand Down
19 changes: 15 additions & 4 deletions src/GafferSceneUI/TransformTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,11 +471,22 @@ void TransformTool::Selection::initWalk( const GafferScene::SceneAlgo::History *
// First, check for a supported node in this history entry
initFromSceneNode( history );

// If we found a node to edit here and the user has requested a
// specific scope, check if the edit is in it.
if( m_upstreamScene && m_editScope )
if( m_upstreamScene )
{
editScopeFound = m_upstreamScene->ancestor<EditScope>() == m_editScope;
const auto upstreamEditScope = m_upstreamScene->ancestor<EditScope>();
if( m_editScope )
{
// If we found a node to edit here and the user has requested a
// specific scope, check if the edit is in it.
editScopeFound = upstreamEditScope == m_editScope;
}
else if( upstreamEditScope )
{
// We don't allow editing if the user hasn't requested a specific scope
// and the upstream edit is inside an EditScope.
m_warning = "Source is in an EditScope. Change scope to " + displayName( upstreamEditScope ) + " to edit";
m_editable = false;
}
}
}

Expand Down

0 comments on commit cc9cca1

Please sign in to comment.