Python Addin and Undo/Redo

1265
6
06-23-2012 08:16 AM
MarkHolder
New Contributor III
There seems to be no way for a Python addin to access the startOperation and stopOperation functions provided by the Editor class. A stand-alone script can use them, but I'd like to be able to click a button provided by an addin and update some data in the map and have the edit appear in the undo/redo stack as was possible in VBA.

Am I missing something? I can do the editing part, but the edits do not appear in the undo/redo stack automatically, and I can't get the Editor class to work.

The API does not include a label for the stopOperation() method as VBA had, so it looks as if the Editor class was not designed to work with Python addins.
Tags (2)
0 Kudos
6 Replies
NobbirAhmed
Esri Regular Contributor
Hi Mark, this code works for me. I select few features I want to delete and click on the Button. The selected features get deleted and active view is updated.


class ButtonClass1(object):

    """Implementation for UndoRedo_addin.button (Button)"""
    def __init__(self):
        self.enabled = True
        self.checked = False
       
    def onClick(self):

        edit = arcpy.da.Editor(r'C:\addin\projects\EditSession\f.gdb')
        edit.startEditing()
        edit.startOperation()
        arcpy.DeleteFeatures_management("fishparcels_line")
        arcpy.RefreshActiveView()
        edit.stopOperation()
        edit.stopEditing(True)
        arcpy.RefreshActiveView()


May be I don not understand what you are trying to do. But, you see both stopEditing and stopOperation are available through the Editor class.
0 Kudos
MarkHolder
New Contributor III
Thanks for the reply. Sorry I didn't notice it sooner.

My question was more about getting a Python add-in to integrate with ArcMap's undo/redo stack like VBA. Your example works, but does not address 2 things I'm interested in:


1) Accessing the workspace ArcMap is currently editing

2) Setting a name for the operation, so ArcMap's undo button can say what would be undone.

It does not look like Python add-in's can do this.

Thanks,
Mark
0 Kudos
KerryAlley
Occasional Contributor
Thanks for the reply. Sorry I didn't notice it sooner.

My question was more about getting a Python add-in to integrate with ArcMap's undo/redo stack like VBA. Your example works, but does not address 2 things I'm interested in:


1) Accessing the workspace ArcMap is currently editing

2) Setting a name for the operation, so ArcMap's undo button can say what would be undone.

It does not look like Python add-in's can do this.

Thanks,
Mark


Mark (or anybody),

Did you find a solution to this issue?  I'm assuming there is one, otherwise I can't see the point of running an editing session from within an add-in.

Kerry
0 Kudos
NobbirAhmed
Esri Regular Contributor
I think what Mark is looking for is an equivalent of Editor extension of .Net. If so, that is not possible with Python add-in.

However, you can listen to various editing events from a Python add-in.

Check out the help topic on Application Extension - you'll find the following editing events can be listened to:

onEditorSelectionChanged
onCurrentLayerChanged
onCurrentTAskChanged
onStartEditing
onStopEditing
onStopOperation
beforeStopOperation
onStopOperation
onSaveEdits

... and few more.
0 Kudos
JohnDye
Regular Contributor
Mark & Nobbir,

Actually, you can access the .NET Editor and functions through Python Addins. It's a bit of a workaround though. What you need to do is directly reference the .NET ProgID for the builtin function you are looking for in the Config.xml. In your case, you need to alter your Python Addin button refID to directly reference the .NET ProgID for the Start/Stop/Save Edit Commands. Since you would then be directly accessing the .NET Editor functions, that should perform the edits in the fashion you desire.

Jason Pardy did a really good blog post on this in August. Give it a read, I think it will solve your problem.

Hope it helps,
John
0 Kudos
ThomasLaxson
New Contributor III

No need to mess around with ArcObjects and .NET. You can create your own edit stack. You can simply set a global variable that tracks the edits and is then accessible to other buttons.

For example, depending on the complexity of the edits performed by your add-in, you could have a global list to which you append information about the edits.

Here's an example of a simple edit button (it just buffers the selected features) with undo and redo buttons. Note that these are not the built-in undo/redo buttons, but Add-In buttons that you create. The edits here will be in a separate stack from the built-in edits.

import arcpy
import pythonaddins

MXD = arcpy.mapping.MapDocument('CURRENT')
FN_SHAPE = 'SHAPE@'

# Global variables that will store the edit histories forward and backward
undo_stack = list()
redo_stack = list()

class MakeEdits(object):
    def __init__(self):
        self.enabled = True
        self.checked = False

    def onClick(self):
        # Get the selected layer in the table of contents
        lyr = pythonaddins.GetSelectedTOCLayerOrDataFrame()

        # Get a count of selected features in the selected layer
        fid_set = arcpy.Describe(lyr).FIDSet
        if fid_set == '':
            count = 0
        else:
            count = len(fid_set.split(';'))

        # If at least one feature is selected
        if count > 0:
            # Enable global modification of the undo_stack
            global undo_stack

            # Get the name of the layer's OID field
            fn_oid = arcpy.Describe(lyr).OIDFieldName

            with arcpy.da.UpdateCursor(lyr, [fn_oid, FN_SHAPE]) as cur:
                for row in cur:
                    # Add the relevant details to the editing stack
                    undo_stack.append((lyr, fn_oid, row[0], row[1]))
                    # An arbitrary example edit
                    row[1] = row[1].buffer(500)
                    cur.updateRow(row)

            arcpy.RefreshActiveView()


class RedoEdits(object):
    def __init__(self):
        self.enabled = True
        self.checked = False

    def onClick(self):
        global undo_stack
        global redo_stack

        if len(redo_stack) > 0:
            # Get the last item in the edit stack
            edit = redo_stack.pop()
            # Clear any existing selection on the layer, or else the cursor may
            #   not hit the necessary feature
            arcpy.SelectLayerByAttribute_management(edit[0], 'CLEAR_SELECTION')
            # A where clause to select the most recently edited feature
            wc = '{} = {}'.format(edit[1], edit[2])

            with arcpy.da.UpdateCursor(edit[0], FN_SHAPE, wc) as cur:
                for row in cur:
                    # Update the undo edit stack
                    edit_out = list(edit[:-1])
                    edit_out.append(row[0])
                    undo_stack.append(edit_out)
                    # Apply the edit
                    row[0] = edit[3]
                    cur.updateRow(row)
                    break

        arcpy.RefreshActiveView()


class UndoEdits(object):
    def __init__(self):
        self.enabled = True
        self.checked = False

    def onClick(self):
        global undo_stack
        global redo_stack

        if len(undo_stack) > 0:
            edit = undo_stack.pop()

            arcpy.SelectLayerByAttribute_management(edit[0], 'CLEAR_SELECTION')
            wc = '{} = {}'.format(edit[1], edit[2])

            with arcpy.da.UpdateCursor(edit[0], FN_SHAPE, wc) as cur:
                for row in cur:
                    edit_out = list(edit[:-1])
                    edit_out.append(row[0])
                    redo_stack.append(edit_out)
                    row[0] = edit[3]
                    cur.updateRow(row)
                    break

        arcpy.RefreshActiveView()

        return
0 Kudos