Close ArcMap using Python AddIn Extension

3647
15
Jump to solution
09-09-2013 03:58 AM
EricMahaffey
New Contributor III
I've written a couple of Python scripts that open ArcMap, run a Python AddIn Extension, then hopefully close ArcMap.  As an MXD document is opened, the AddIn extension runs the [HTML]def beforeCloseDocument(self):[/HTML] function.  However I can't seem to figure out how to close ArcMap after the MXD has completely opened.  Basically I'm trying to work around a known bug in which the ExportToPDF and PrintMap functions will not work correctly with layouts that have attribute tables embeded in them.  Here's the code from my AddIn Extension.  Any advice would be much apprciated.  I'm hitting a wall here, and just can't seem to get anywhere with this.

[HTML]import arcpy
import pythonaddins
import os
import time


class ExtensionClass1(object):
    """Implementation for PrintToPDF_AddIn_addin.extension2 (Extension)"""
    def __init__(self):
        # For performance considerations, please remove all unused methods in this class.
        self.enabled = True

    def openDocument(self):
        mxd = arcpy.mapping.MapDocument("CURRENT")
        if mxd.description == 'Print to PDF':
            #time.sleep(30)
            #os.system("pause")
            os.system('taskkill /IM Arcmap*')#is killing it too soon
            #time.sleep(15)
        pass
           
    def beforeCloseDocument(self):
        #mxd = arcpy.mapping.MapDocument("CURRENT")
        if mxd.description == 'Print to PDF':
            arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")
        pass[/HTML]
Tags (2)
0 Kudos
1 Solution

Accepted Solutions
EricMahaffey
New Contributor III
OK, here's what I finally figured out.  It all comes down to the "Zoom To Percentage" in the map layout.  All along I???ve noticed that printing to a PDF file seemed to build the layout tables correctly, but the table would be too small.

In ArcMap:
[ATTACH=CONFIG]27486[/ATTACH]

The resulting Output PDF:
[ATTACH=CONFIG]27487[/ATTACH]

However when I changed the "Zoom to Percentage" in the layout, the results looked like this:

In ArcMap:
[ATTACH=CONFIG]27488[/ATTACH]

The resulting Output PDF:
[ATTACH=CONFIG]27489[/ATTACH]

Go figure as to why, but it works!  For me my magic number was 245%.  I think it'll vary for each user depending on how they have their toolbars setup, etc.  This value also applied to both 8.5x11 and 11x17 landscape layouts.

So as a recap I used this script to launch ArcMap and open the MXD file:
import arcpy import subprocess  strArcMapPath = r"C:\Program Files (x86)\ArcGIS\Desktop10.1\bin\ArcMap.exe" strMxd = r"D:\Users\Products\PSA Map Updates\Automation\PSA Location Map - Final.mxd"  objNewProcess = subprocess.Popen([strArcMapPath, strMxd]) objNewProcess.wait() # this line will wait for the new process to end  print "Script completed successfully."


Then this one as my Python AddIn script:
import arcpy import pythonaddins  class ExtensionClass1(object):     """Implementation for PrintToPDF_AddIn_addin.extension2 (Extension)"""     def __init__(self):         # For performance considerations, please remove all unused methods in this class.         self.enabled = True      def openDocument(self):         mxd = arcpy.mapping.MapDocument("CURRENT")         if mxd.description == 'Print to PDF':             arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")


Now I was orignally trying to close ArcMap in order to execute the PrintMap function before close, however this method works at open.  I still can't believe it.  Keep in mind that this process requires a PDF print driver of some sort in order to work.  I have Adobe Acrobat installed which includes an "Adobe PDF" driver, however there are a few freebies out there that shouldwork just as well (i.e. CutePDF, etc...)

I plan to forward this to out customer support rep to see if that will help move things along with the bug fix.  Thanks for all the input, and assistance.  If anyone has any questions feel free to contact me.

View solution in original post

0 Kudos
15 Replies
StacyRendall1
Occasional Contributor III
Something like this should work, using the Psutil library for Python:
import psutil
# list of process names that we might want to terminate
badList = ['ArcMap.exe', 'AppROT.exe', 'ArcGISConnection.exe', 'ArcGISCacheMgr.exe']

# generate list of processes to terminate
badProcesses = [p for p in psutil.process_iter() if p.name in badList]

# terminate the bad processes
[p.terminate() for p in badProcesses]


It is a bit zealous about terminating all the Arc processes, you can probably get away with changing badList to just:
badList = ['ArcMap.exe']
0 Kudos
EricMahaffey
New Contributor III
Thanks for the info Stacey.  I'm able to get the ArcMap process to teminate using [HTML]os.system('taskkill /IM Arcmap*'[/HTML], but I can't seem to get it to run after a delay.  You'll see in my posted code, that I'm using an AddIn Extension that runs as soon as the MXD opens.  However, the process executes too soon, and tries to kill ArcMap before the MXD fully opens.  When I add time.sleep() or similar it just delays the openeing of the document, not the next part of the script which is the arcpy.mapping.PrintMap function.  Because of the limitations with AddIn functions, PrintToPDF has to run before the document closes in order to capture all the tables after they've fully loaded.  The whole thing has to be timed just right so that the layout fully assembles, then the PritnToPDF function executes.
0 Kudos
markdenil
Occasional Contributor III
I seems clear that you are using the os module to kill the Arc process, so the document never gets to close.
Instead it is stabbed in the back by the os.

Maybe you need to put everything inthe openDocument function.
pause there to alow the tables to load before runing printToPDF
and only then kill the process off (after the printing has finished).
0 Kudos
EricMahaffey
New Contributor III
Mark, thanks for the suggestions.  I've tried running the "arcpy.mapping.PrintMap" function in the "def openDocument(self): " function.  However, I can't get the process to stall long enough for the layout to fully assemble the tables before the PrintMap executes.  What I get are tiny tables, or incomplete ones.  I tried to use time.sleep() to pause the script while the MXD is opening, but all that does is pause the open process and not the script.  Any ideas how I can properly pause the script while the MXD is opening and assembling?
0 Kudos
StacyRendall1
Occasional Contributor III
Thanks for the info Stacey.  I'm able to get the ArcMap process to teminate using [HTML]os.system('taskkill /IM Arcmap*'[/HTML], but I can't seem to get it to run after a delay.  You'll see in my posted code, that I'm using an AddIn Extension that runs as soon as the MXD opens.  However, the process executes too soon, and tries to kill ArcMap before the MXD fully opens.  When I add time.sleep() or similar it just delays the openeing of the document, not the next part of the script which is the arcpy.mapping.PrintMap function.  Because of the limitations with AddIn functions, PrintToPDF has to run before the document closes in order to capture all the tables after they've fully loaded.  The whole thing has to be timed just right so that the layout fully assembles, then the PritnToPDF function executes.


Oh, I failed to read your question properly (...at all...?). Sorry!

I also noticed that the very bottom line of code from the initial post contains a pass statement. This should not be there.

To find the solution for a problem like this you need to strip everything back to the simplest possible set of operations that works, then add in the complexity. I would suggest that you initially work in a script; if/once you get a series of steps that does work, you can put it back into the class form. Here is a script that, I think, is doing what you want. It probably won't work either, but it is a lot more clear what is happening and when:
import arcpy
import os

mxd = arcpy.mapping.MapDocument("CURRENT")
if mxd.description == 'Print to PDF':
    arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")
    os.system('taskkill /IM Arcmap*')


Now, to get ArcMap to finish before closing you could try to get the code to perform some trivial operation the MXD after it is printed, but before it tries to close. This might be enough to force it to complete the print before the trivial operation is allowed to start, i.e. List Layers:
import arcpy
import os

mxd = arcpy.mapping.MapDocument("CURRENT")
if mxd.description == 'Print to PDF':
    arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")
    arcpy.mapping.ListLayers(mxd)
    os.system('taskkill /IM Arcmap*')
0 Kudos
EricMahaffey
New Contributor III
Stacy,

I think you're on to something.  When I test each of the pieces in the Python command window within ArcMap they do as expected.  However, linking everything together based on timing is the difficult part.  So up until now I've been going with the idea of using a Python AddIn Extension so that my code executes within the MXD after a certain event (i.e openDocument, beforeCloseDocument, closeDocument, and even itemAdded).  No matter what I try I can't seem to get my main function "arcpy.mapping.PrintMap" to execute at the right time.  I took some of your advice and tried this:

[HTML]import arcpy
import pythonaddins
import os
import time


class ExtensionClass1(object):
    """Implementation for PrintToPDF_AddIn_addin.extension2 (Extension)"""
    def __init__(self):
        # For performance considerations, please remove all unused methods in this class.
        self.enabled = True

    def openDocument(self):
        mxd = arcpy.mapping.MapDocument("CURRENT")
        if mxd.description == 'Print to PDF':
            layers = arcpy.mapping.ListLayers(mxd, "", "Layers"):
                print layers
                time.sleep(1)
            os.system('taskkill /IM Arcmap*')

    def beforeCloseDocument(self):
            arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")[/HTML]

So while opening the MXD it's listing the layers, but the file isn't continuing to open as this process occurs.  ArcMap waits until all the layers have been listed before it completely assembles the layout.  When I added the os.system('taskkill /IM Arcmap*') it would list all the layers then crush ArcMap before it could execute the arcpy.mapping.PrintMap.

It's all about timing, and I can't seem to control when things happen. 😞
0 Kudos
StacyRendall1
Occasional Contributor III
Eric,

as I said before: my advice is to leave the Python AddIn Extension until you get something that works from a script. A script is not the same as the command window: the time it takes for you to enter the commands (or copy/paste them) might be enough for Arc to finish the preceding operations.

The reason I am asking you to do this is that I have no idea how the Python AddIn Extension works, what its execution order is, if it tries to use multiple threads, etc. Once you have something that works from a script it will be simple to implement it within the Python AddIn Extension.

To save and run the script:

  1. copy the first block of code from my previous post (the one without ListLayers) into notepad/IDLE

  2. save it somewhere as test1.py

  3. open a command prompt in the folder where test1.py is residing

  4. locate your ArcGIS Python install (10.0 usually places it at C:\Python26\ArcGIS10.0\, 10.1 usually places it at C:\Python27\ArcGIS10.1\)

  5. test it by entering the full path to python.exe at the command line, i.e.: C:\Python27\ArcGIS10.1\python.exe

  6. this should open the interpreter, and you can test it is the correct install by running import arcpy; if everything is OK it will report nothing, just return you to the python command prompt >>>

  7. once you have both the above steps working use exit() to leave the interpreter and run the script like so: C:\Python27\ArcGIS10.1\python.exe test1.py

What is the output?

Now save the second block of code from my previous post as test2.py and repeat the relevant steps of the above process. What is the output now?
0 Kudos
EricMahaffey
New Contributor III
Stacy,

Thanks again.  At this point in my development I'm way past testing each stage of what I'm trying to do (although I did do as you asked and tested each piece but had no luck).  That's why I'm in the situation that I'm currently in.  Maybe it'll help if I explain why I'm having to do all of these steps in just the right method and order.

For a few years now, ESRI has not responded to a bug that's been reported multiple times (NIM062177 and NIM063441 - "ExportToPDF using arcpy does not export a layer attribute table embedded in a layout").  They claim that the bug was rejected because it is "beyond the design of the software".  However, the ExportToPDF function works fine with layouts without tables.  So it appears that it was never fully tested.  So, as a result I am trying to work around the bug, and get my layouts with embeded attribute tables to produce PDF files.

What I do know is that if I open my MXD manually in ArcMap and Export to a PDF it drops the wireframe around the tables (but produces the data.  If I do this through code only, I get no data).  However, if I use the Adobe PDF print driver and print to a file, it works.  So, from this I know that I have to do the following things in this exact order and timing.

1. Open the MXD in ArcMap
2. Wait for the Layout to fully assemble
3. Print to file using the Adobe PDF print driver
4. Close ArcMap (hopefully)

The reason I opted to use a Python AddIn Extension is it can be executed within the ArcMap application, and not through code only (which doesn't work). 

So as it stands I have 2 Python scripts.  One that launches ArcMap, and opens an MXD, and another which runs in the AddIn Extension:

Open the MXD in ArcMap:

[HTML]import arcpy
import subprocess

strArcMapPath = r"C:\Program Files (x86)\ArcGIS\Desktop10.1\bin\ArcMap.exe"
strMxd = r"D:\Users\Products\PSA Map Updates\Automation\PSA Location Map - Final.mxd"

objNewProcess = subprocess.Popen([strArcMapPath, strMxd])
objNewProcess.wait() # this line will wait for the new process to end

print "Script completed successfully."[/HTML]

Execute Python AddIn Extension:

[HTML]import arcpy
import pythonaddins
import os
import time


class ExtensionClass1(object):
    """Implementation for PrintToPDF_AddIn_addin.extension2 (Extension)"""
    def __init__(self):
        # For performance considerations, please remove all unused methods in this class.
        self.enabled = True

    def openDocument(self):
        mxd = arcpy.mapping.MapDocument("CURRENT")
        if mxd.description == 'Print to PDF':
            layers = arcpy.mapping.ListLayers(mxd, "", "Layers"):
                print layers
                time.sleep(1)
            os.system('taskkill /IM Arcmap*')

    def beforeCloseDocument(self):
            arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")[/HTML]

As ArcMap opens, the AddIn Extension executes another Python script (shown above) which queries the MXD properties for a piece of text "Print to PDF" to determine if the map should be Printed To a PDF.  If the Description Property does not meet the query it just opens as usual, if it does, I'd like for it to roll into the PrintMap function which produces the PDF file.

The kicker here is that I'm limited to the functions available within the extension class (at least according to the documentation).  These functions appear to be run only if a certain event occurs (i.e openDocument, beforeCloseDocument, closeDocument, etc.).  That being said I'm trying to figure out a way to execute the "arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")" function inside the extension class as the result of an occurence.  When I do this after openDocument, it runs too quickly and the tables do not get added to the PDF correctly.  When I try to stall the PrintMap function at openDocument (using ListLayers, time.sleep(), etc.) the actual opening of the file gets stalled.  So I opted to try to generate the PDF beforeCloseDocument which is ultimately why I started this thread.  I have to programatically close the MXD in order for the PrintMap function to execute.

Maybe there's a way to build a custom extension class function that'll do all this, however, my knowledge of Python is limited to what I've been able to do so far.

Hopefully this will help you (or anyone out there) understand what I trying to accomplish.
0 Kudos
EricMahaffey
New Contributor III
Stacy,

Thanks again.  I ran through the tests that you recommended and unfortunately didn't have any luck.  At this point I've tested all the steps that need to be run in ArcMap's command window, Python Idle, and Python Command line.   That's why I'm in the situation that I'm currently in.  Maybe it'll help if I explain why I'm having to do all of these steps using just the right method and order.

For a few years now, ESRI has not responded to a bug that's been reported multiple times (NIM062177 and NIM063441 - "ExportToPDF using arcpy does not export a layer attribute table embedded in a layout").  They claim that the bug was rejected because it is "beyond the design of the software".  However, the ExportToPDF function works fine with layouts without tables.  So it appears that it was never fully tested.  So, as a result I am trying to work around the bug, and get my layouts with embeded attribute tables to produce PDF files.

What I do know is that if I open my MXD manually in ArcMap and Export to a PDF it drops the wireframe around the tables (but produces the data.  If I do this through code only, I get no data).  However, if I use the Adobe PDF print driver and print to a file, it works.  So, from this I know that I have to do the following things in this exact order and timing.

1. Open the MXD in ArcMap
2. Wait for the Layout to fully assemble
3. Print to file using the Adobe PDF print driver
4. Close ArcMap (hopefully)

The reason I opted to use a Python AddIn Extension is it can be executed within the ArcMap application, and not through code only (which doesn't work). 

So as it stands I have 2 Python scripts.  One that launches ArcMap, and opens an MXD, and another which runs in the AddIn Extension:

Open the MXD in ArcMap:

[HTML]import arcpy
import subprocess

strArcMapPath = r"C:\Program Files (x86)\ArcGIS\Desktop10.1\bin\ArcMap.exe"
strMxd = r"D:\Users\Products\PSA Map Updates\Automation\PSA Location Map - Final.mxd"

objNewProcess = subprocess.Popen([strArcMapPath, strMxd])
objNewProcess.wait() # this line will wait for the new process to end

print "Script completed successfully."[/HTML]

Execute Python AddIn Extension:

[HTML]import arcpy
import pythonaddins
import os
import time


class ExtensionClass1(object):
    """Implementation for PrintToPDF_AddIn_addin.extension2 (Extension)"""
    def __init__(self):
        # For performance considerations, please remove all unused methods in this class.
        self.enabled = True

    def openDocument(self):
        mxd = arcpy.mapping.MapDocument("CURRENT")
        if mxd.description == 'Print to PDF':
            layers = arcpy.mapping.ListLayers(mxd, "", "Layers"):
                print layers
                time.sleep(1)
            os.system('taskkill /IM Arcmap*')

    def beforeCloseDocument(self):
            arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")[/HTML]

As ArcMap opens, the AddIn Extension executes another Python script (shown above) which queries the MXD properties for a piece of text "Print to PDF" to determine if the map should be Printed To a PDF.  If the Description Property does not meet the query it just opens as usual, if it does, I'd like for it to roll into the PrintMap function which produces the PDF file.

The kicker here is that I'm limited to the functions available within the extension class (at least according to the documentation).  These functions appear to be run only if a certain event occurs (i.e openDocument, beforeCloseDocument, closeDocument, etc.).  That being said I'm trying to figure out a way to execute the "arcpy.mapping.PrintMap(mxd, r"Adobe PDF", "PAGE_LAYOUT")" function inside the extension class as the result of an occurence.  When I do this after openDocument, it runs too quickly and the tables do not get added to the PDF correctly.  When I try to stall the PrintMap function at openDocument (using ListLayers, time.sleep(), etc.) the actual opening of the file gets stalled.  So I opted to try to generate the PDF beforeCloseDocument which is ultimately why I started this thread.  I have to programatically close the MXD in order for the PrintMap function to execute.

Maybe there's a way to build a custom extension class function that'll do all this, however, my knowledge of Python is limited to what I've been able to do so far.

Hopefully this will help you (or anyone out there) understand what I trying to accomplish.
0 Kudos