That was a helpful link Wayne, especially if I end up having to write code to put edit operations on the operation stack! I assume that my issue reflects my shallow understanding of ArcObjects, but I guess there might actually be an unexpected consequence of using the Python addin framework. If I remove all references to the Editor from the addin, it returns errors when run outside of an editing session (expected when editing SDE data). However, if run from within an ongoing edit session, it runs as expected except that the features are not undoable and are permanent (ending without saving is futile).I forgot to mention that this particular addin has two buttons, one to select a feature, the other to create features. The undo button will actually select, in reverse order, the features that had been selected using that addin button (and usually an adjacent feature as well for good measure, for who knows what reason). The redo button reverses the sequence of selected features, but without including any adjacent features. Another stack puzzle. 🙂 And further evidence that Python addins have a weird way of interacting with operation stacks!I've pasted my addin script below. I've also pasted the function "Snippets.ArcMap_GetIworkspaceEdit()" that I call from the addin. That function is a very slightly modified version of the Mark Cederholm's function "Snippets.ArcMap_GetEditWorkspace()" from his Snippets.py script available from this page. My function calls other functions from Mark's Snippets.py as well.Addin Script:#ShieldToolComtypes_addin.py
#KAlley 2013_11_11
#VT Agency of Transportation
#
#Based on ShieldTool_addin.py (KAlley 2013-03-12)
#In this script, lines of code relevent to using arcpy.da.Editor are commented out
import arcpy, time, os, sys, math
sys.path.append("V:\Projects\Shared\kalley\PythonScripts")
import comtypes, Snippets
import pythonaddins
class SelectRoadArcClass(object):
"""Implementation for TwoInputsAddin_addin_B.tool (Tool)"""
def __init__(self):
self.enabled = True
self.shape = "NONE" # Can set to "Line", "Circle" or "Rectangle" for interactive shape drawing and to activate the onLine/Polygon/Circle event sinks.
def onMouseDownMap(self, x, y, button, shift):
#start_time = time.time() #only used to compare run-times of different versions of the script
arcpy.env.overwriteOutput = True
path = r'V:\Projects\Shared\kalley\ToolDevelopment\ShieldToolComtypes\Install'
print "Comtypes Version of the Shield Tool AddIn"
#rdsmall_dataSource = r"GDB_HMS.HMSADMIN.rdsmall_arc"
rdsmall_dataSource = r"GDB_HMSDev.HMSADMIN.rdsmall_arc_kalley"
mxd = arcpy.mapping.MapDocument('current')
for lyr in arcpy.mapping.ListLayers(mxd):
if lyr.supports("dataSource"):
if str(lyr.dataSource).endswith(rdsmall_dataSource):
rdsmall_lyr = lyr
point = arcpy.Point()
point.X = x
point.Y = y
pointGeometry = arcpy.PointGeometry(point)
arcpy.SelectLayerByLocation_management(rdsmall_lyr, "WITHIN_A_DISTANCE", pointGeometry, "40 Meters", "NEW_SELECTION")
#print time.time() - start_time, "seconds"
class CreateShieldClass(object):
"""Implementation for TwoInputsAddin_addin_A.tool (Tool)"""
def __init__(self):
self.enabled = True
self.cursor = 3
self.shape = "LINE" # Can set to "Line", "Circle" or "Rectangle" for interactive shape drawing and to activate the onLine/Polygon/Circle event sinks.
def onLine(self, line_geometry):
start_time = time.time() #only used to compare rough run-times of different versions of the script
arcpy.env.overwriteOutput = True
#Hard-wiring the exact feature classes that will be edited with shield tool:
##VTrans Data:
##shield_points_dataSource = r"GDB_HMS.HMSADMIN.hms_shields_points"
##shield_arcs_dataSource = r"GDB_HMS.HMSADMIN.hms_shields_arcs"
##rdsmall_dataSource = r"GDB_HMS.HMSADMIN.rdsmall_arc"
#Development Sandbox copy of VTrans data:
shield_points_dataSource = r"GDB_HMSDev.HMSADMIN.hms_shields_points_kalley"
shield_arcs_dataSource = r"GDB_HMSDev.HMSADMIN.hms_shields_arcs_kalley"
rdsmall_dataSource = r"GDB_HMSDev.HMSADMIN.rdsmall_arc_kalley"
#mxd = arcpy.mapping.MapDocument(r"K:\ToolDevelopment\ToolDevelopmentSDE.mxd") #for testing script independently of add-in
mxd = arcpy.mapping.MapDocument('current')
#for new shields to appear in current mxd, this script must refer to layers in the current mxd
lyrs = arcpy.mapping.ListLayers(mxd)
for lyr in arcpy.mapping.ListLayers(mxd):
if lyr.supports("dataSource"):
if str(lyr.dataSource).endswith(shield_points_dataSource):
shield_points_lyr = lyr
if str(lyr.dataSource).endswith(shield_arcs_dataSource):
shield_arcs_lyr = lyr
if str(lyr.dataSource).endswith(rdsmall_dataSource):
rdsmall_lyr = lyr
if not (shield_points_lyr and shield_arcs_lyr and rdsmall_lyr):
print "Shield Not Created..."
print "Must have at least 3 layers in this mxd: shield points, shield arcs, and rdsmall"
#only allow shield creation if a single road arc is selected
num_road_arcs = int(arcpy.GetCount_management(rdsmall_lyr).getOutput(0))
if num_road_arcs == 1:
##Code used if using arcpy.da.Editor
##workspace = rdsmall_lyr.workspacePath #for .gdb *and* SDE connection (refers to .sde file in DatabaseConnections)
##arcpy.env.workspace = workspace
##edit_py = arcpy.da.Editor(workspace)
##edit_py.startEditing() #with undo, multiuser mode
#Code used if accessing preexisting editing session with comtypes/ArcObjects
edit = Snippets.ArcMap_GetIWorkspaceEdit(bStandalone=False)
if edit is None:
print "Must be in an editing session to use this tool"
return
#identify maximum SHI_GRP_ID value currently used in shields
max_id = sorted(arcpy.da.SearchCursor(shield_points_lyr.dataSource, "SHI_GRP_ID"), reverse = 1)[0][0]
#collect selected road arc attributes
rdsmall_values = sorted(arcpy.da.SearchCursor(rdsmall_lyr, ["UA", "FAID_S", "CTCODE", "RTNUMBER", "AOTCLASS"]))
#initialize the new line as the input line_geometry (will change if fix_length == True)
new_line = line_geometry
#create shield point and populate attributes
new_points_Cur = arcpy.da.InsertCursor(shield_points_lyr, ["SHAPE@XY", "UA", "FAID_S", "CTCODE", "RTNO", "AOTCLASS", "SHI_GRP_ID", "ISVISIBLE", "SUBINSET"])
test = (new_line.firstPoint,) + tuple(rdsmall_values) + (max_id + 1,)
test_tuple = ((test[0].X, test[0].Y), test[1][0], test[1][1], str(test[1][2]), str(test[1][3]), test[1][4], test[2], 1, "N")
##edit_py.startOperation()
edit.StartEditOperation
new_points_Cur.insertRow(test_tuple)
##edit_py.startOperation()
edit.StopEditOperation
del new_points_Cur
#create shield arc and populate SHI_GRP_ID
new_arc_Cur = arcpy.da.InsertCursor(shield_arcs_lyr.dataSource, ["SHAPE@", "SHI_GRP_ID"])
##edit_py.startOperation()
edit.StartEditOperation
new_arc_Cur.insertRow([new_line, max_id + 1])
##edit_py.stopOperation()
edit.StopEditOperation
del new_arc_Cur
##edit_py.stopEditing(True) #save changes
arcpy.SelectLayerByAttribute_management(rdsmall_lyr, "CLEAR_SELECTION")
arcpy.RefreshActiveView()
else:
print "Shield Not Created..."
print "Must select a single arc from rdsmall"
#print time.time() - start_time, "seconds"
function Snippets.ArcMap_GetIWorkspaceEdit:def ArcMap_GetIWorkspaceEdit(bStandalone=False):
GetDesktopModules()
if bStandalone:
InitStandalone()
pApp = GetApp()
else:
pApp = GetCurrentApp()
GetModule("esriEditor.olb")
import comtypes.gen.esriSystem as esriSystem
import comtypes.gen.esriEditor as esriEditor
import comtypes.gen.esriGeoDatabase as esriGeoDatabase
pID = NewObj(esriSystem.UID, esriSystem.IUID)
pID.Value = CLSID(esriEditor.Editor)
pExt = pApp.FindExtensionByCLSID(pID)
pEditor = CType(pExt, esriEditor.IEditor)
if pEditor.EditState == esriEditor.esriStateEditing:
pWS = pEditor.EditWorkspace
pWSE = CType(pWS, esriGeoDatabase.IWorkspaceEdit)
pDS = CType(pWS, esriGeoDatabase.IDataset)
print "Workspace name: " + pDS.BrowseName
print "Workspace category: " + pDS.Category
return pWSE
return