Ejemplo n.º 1
0
 def wait_for_exit(self, abortHandler, pollFunction = None):
     """
     Wait for the process to exit (sleeping by 0.05 seconds in a
     loop).  Calls pollFunction each time around the loop if it is
     specified.  Return its exitcode.  Call this only after the
     process was successfully started using self.start() or
     self.launch().
     """
     abortPressCount = 0
     while (not self.state() == QProcess.NotRunning):
         if (abortHandler):
             pc = abortHandler.getPressCount()
             if (pc > abortPressCount):
                 abortPressCount = pc
                 if (abortPressCount > 1):
                     self.terminate()
                 else:
                     self.kill()
         env.call_qApp_processEvents() #bruce 050908 replaced qApp.processEvents()
             #k is this required for us to get the slot calls for stdout / stderr ?
             # I don't know, but we want it even if not.
         if (pollFunction):
             pollFunction()
         time.sleep(0.05)
     if (abortHandler):
         abortHandler.finish()
     return self.exitCode()
Ejemplo n.º 2
0
 def wait_for_exit(self, abortHandler, pollFunction=None):
     """
     Wait for the process to exit (sleeping by 0.05 seconds in a
     loop).  Calls pollFunction each time around the loop if it is
     specified.  Return its exitcode.  Call this only after the
     process was successfully started using self.start() or
     self.launch().
     """
     abortPressCount = 0
     while (not self.state() == QProcess.NotRunning):
         if (abortHandler):
             pc = abortHandler.getPressCount()
             if (pc > abortPressCount):
                 abortPressCount = pc
                 if (abortPressCount > 1):
                     self.terminate()
                 else:
                     self.kill()
         env.call_qApp_processEvents(
         )  #bruce 050908 replaced qApp.processEvents()
         #k is this required for us to get the slot calls for stdout / stderr ?
         # I don't know, but we want it even if not.
         if (pollFunction):
             pollFunction()
         time.sleep(0.05)
     if (abortHandler):
         abortHandler.finish()
     return self.exitCode()
Ejemplo n.º 3
0
    def gl_update_duration(self, new_part=False):
        """
        Redraw GLPane and update the repaint duration variable <self._repaint_duration>
        used by animateToView() to compute the proper number of animation frames.
        Redraws the GLPane twice if <new_part> is True and only saves the repaint 
        duration of the second redraw.  This is needed in the case of drawing a newly opened part,
        which takes much longer to draw the first time than the second (or thereafter).
        """
        # The first redraw of a new part takes much longer than the second redraw.
        if new_part:
            self.gl_update()
            env.call_qApp_processEvents()  # Required!

        self._repaint_start_time = time.time()
        self.gl_update()
        env.call_qApp_processEvents(
        )  # This forces the GLPane to update before executing the next gl_update().
        self._repaint_end_time = time.time()

        self._repaint_duration = max(
            MIN_REPAINT_TIME,
            self._repaint_end_time - self._repaint_start_time)

        # _last_few_repaint_times is currently unused. May need it later.  Mark 060116.
        # (bruce 080912: disabling it. if we revive it, something needs to initialize it to [].)
        ## self._last_few_repaint_times.append( self._repaint_duration)
        ## self._last_few_repaint_times = self._last_few_repaint_times[-5:] # keep at most the last five times

        ##if new_part:
        ##    print "new part, repaint duration = ", self._repaint_duration
        ##else:
        ##    print "repaint duration = ", self._repaint_duration

        return
Ejemplo n.º 4
0
    def gl_update_duration(self, new_part = False):
        """
        Redraw GLPane and update the repaint duration variable <self._repaint_duration>
        used by animateToView() to compute the proper number of animation frames.
        Redraws the GLPane twice if <new_part> is True and only saves the repaint 
        duration of the second redraw.  This is needed in the case of drawing a newly opened part,
        which takes much longer to draw the first time than the second (or thereafter).
        """
        # The first redraw of a new part takes much longer than the second redraw.
        if new_part: 
            self.gl_update()
            env.call_qApp_processEvents() # Required!

        self._repaint_start_time = time.time()
        self.gl_update()
        env.call_qApp_processEvents() # This forces the GLPane to update before executing the next gl_update().
        self._repaint_end_time = time.time()

        self._repaint_duration =  max(MIN_REPAINT_TIME, self._repaint_end_time - self._repaint_start_time)

        # _last_few_repaint_times is currently unused. May need it later.  Mark 060116.
        # (bruce 080912: disabling it. if we revive it, something needs to initialize it to [].)
        ## self._last_few_repaint_times.append( self._repaint_duration)
        ## self._last_few_repaint_times = self._last_few_repaint_times[-5:] # keep at most the last five times

        ##if new_part:
        ##    print "new part, repaint duration = ", self._repaint_duration
        ##else:
        ##    print "repaint duration = ", self._repaint_duration

        return
Ejemplo n.º 5
0
    def show_progressbar_and_stop_button(self, progressReporter, cmdname="<unknown command>", showElapsedTime=False):
        """
        Display the statusbar's progressbar and stop button, and
        update it based on calls to the progressReporter.

        When the progressReporter indicates completion, hide the
        progressbar and stop button and return 0. If the user first
        presses the Stop button on the statusbar, hide the progressbar
        and stop button and return 1.

        Parameters:
        progressReporter - See potential implementations below.
        cmdname - name of command (used in some messages and in abort button tooltip)
        showElapsedTime - if True, display duration (in seconds) below progress bar

        Return value: 0 if file reached desired size, 1 if user hit abort button.

        """

        updateInterval = 0.1  # seconds
        startTime = time.time()
        elapsedTime = 0
        displayedElapsedTime = 0

        ###e the following is WRONG if there is more than one task at a time... [bruce 060106 comment]
        self.progressBar.reset()
        self.progressBar.setMaximum(progressReporter.getMaxProgress())
        self.progressBar.setValue(0)
        self.progressBar.show()

        abortHandler = AbortHandler(self, cmdname)

        # Main loop
        while progressReporter.notDoneYet():
            self.progressBar.setValue(progressReporter.getProgress())
            env.call_qApp_processEvents()
            # Process queued events (e.g. clicking Abort button,
            # but could be anything -- no modal dialog involved anymore).

            if showElapsedTime:
                elapsedTime = int(time.time() - startTime)
                if elapsedTime != displayedElapsedTime:
                    displayedElapsedTime = elapsedTime
                    env.history.progress_msg("Elapsed Time: " + hhmmss_str(displayedElapsedTime))

            if abortHandler.getPressCount() > 0:
                env.history.statusbar_msg("Aborted.")
                abortHandler.finish()
                return 1

            time.sleep(updateInterval)  # Take a rest

        # End of Main loop (this only runs if it ended without being aborted)
        self.progressBar.setValue(progressReporter.getMaxProgress())
        time.sleep(updateInterval)  # Give the progress bar a moment to show 100%
        env.history.statusbar_msg("Done.")
        abortHandler.finish()
        return 0
Ejemplo n.º 6
0
 def launchProgressDialog(self):
     """
     """
     stime = time.time()
     self.show()
     while 1:
         env.call_qApp_processEvents()
         if self.Rejected:
             break
         duration = time.time() - stime
         elapmsg = "Elapsed Time: " + hhmmss_str(int(duration))
         self.msgLabel2.setText(elapmsg) 
         time.sleep(0.01)
     return
Ejemplo n.º 7
0
 def launchProgressDialog(self):
     """
     """
     stime = time.time()
     self.show()
     while 1:
         env.call_qApp_processEvents()
         if self.Rejected:
             break
         duration = time.time() - stime
         elapmsg = "Elapsed Time: " + hhmmss_str(int(duration))
         self.msgLabel2.setText(elapmsg)
         time.sleep(0.01)
     return
Ejemplo n.º 8
0
 def _debug_do_benchmark(self):
     # simple graphics benchmark, piotr 080311
     from time import clock
     from utilities.debug import profile
     print "Entering graphics benchmark. Drawing 100 frames... please wait."
     win = self._debug_win
     self.win.resize(1024,768) # resize the window to a constant size
     self.win.glpane.paintGL() 
     # draw once just to make sure the GL context is current
     # piotr 080405
     env.call_qApp_processEvents() # make sure all events were processed
     tm0 = clock()
     profile(self._draw_hundred_frames, self, None)
     tm1 = clock()
     print "Benchmark complete. FPS = ", 100.0/(tm1-tm0)
Ejemplo n.º 9
0
 def _debug_do_benchmark(self):
     # simple graphics benchmark, piotr 080311
     from time import clock
     from utilities.debug import profile
     print "Entering graphics benchmark. Drawing 100 frames... please wait."
     win = self._debug_win
     self.win.resize(1024, 768)  # resize the window to a constant size
     self.win.glpane.paintGL()
     # draw once just to make sure the GL context is current
     # piotr 080405
     env.call_qApp_processEvents()  # make sure all events were processed
     tm0 = clock()
     profile(self._draw_hundred_frames, self, None)
     tm1 = clock()
     print "Benchmark complete. FPS = ", 100.0 / (tm1 - tm0)
Ejemplo n.º 10
0
    def _debug_do_benchmark(self):
        # simple graphics benchmark, piotr 080311
        from time import clock
        print "Entering graphics benchmark. Drawing 100 frames... please wait."
        win = self._debug_win
        self.win.resize(1024, 768)  # resize the window to a constant size

        self.win.glpane.paintGL()
        # draw once just to make sure the GL context is current
        # piotr 080405
        # [BUG: the right way is gl_update -- direct call of paintGL won't
        #  always work, context might not be current -- bruce 090305 comment]

        env.call_qApp_processEvents()  # make sure all events were processed
        tm0 = clock()
        profile_single_call_if_enabled(self._draw_hundred_frames, self, None)
        tm1 = clock()
        print "Benchmark complete. FPS = ", 100.0 / (tm1 - tm0)
        return
Ejemplo n.º 11
0
 def _debug_do_benchmark(self):
     # simple graphics benchmark, piotr 080311
     from time import clock
     print "Entering graphics benchmark. Drawing 100 frames... please wait."
     win = self._debug_win
     self.win.resize(1024,768) # resize the window to a constant size
     
     self.win.glpane.paintGL() 
     # draw once just to make sure the GL context is current
     # piotr 080405
     # [BUG: the right way is gl_update -- direct call of paintGL won't
     #  always work, context might not be current -- bruce 090305 comment]
     
     env.call_qApp_processEvents() # make sure all events were processed
     tm0 = clock()
     profile_single_call_if_enabled(self._draw_hundred_frames, self, None)
     tm1 = clock()
     print "Benchmark complete. FPS = ", 100.0 / (tm1 - tm0)
     return
Ejemplo n.º 12
0
    def _launch_pcgamess(self):
        """
        Run PC GAMESS (Windows only).
        PC GAMESS creates 2 output files:
          - the DAT file, called "PUNCH", is written to the directory from which
            PC GAMESS is started.  This is why we chdir to the Gamess temp directory
            before we run PC GAMESS.
          - the OUT file (aka the log file), which we name jigname.out.
        Returns: 0 = Success
                       1 = Cancelled
                       2 = Failed
        """
        oldir = os.getcwd() # Save current directory
        
        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir) # Change directory to the GAMESS temp directory.
##        print "Current directory is: ", jobDir
        
        DATfile = os.path.join(jobDir, "PUNCH")
        if os.path.exists(DATfile): # Remove any previous DAT (PUNCH) file.
            print "run_pcgamess: Removing DAT file: ", DATfile
            os.remove(DATfile)
        
        # Hours wasted testing this undocumented tripe.  Here's the deal: When using spawnv
        # on Windows, any args that might have spaces must be delimited by double quotes.
        # Mark 050530.
        
        #program = "\"" + self.job_batfile + "\""
        #args = [program, ]
        
        # Here's the infuriating part.  The 2nd arg to spawnv cannot have double quotes, but the
        # first arg in args (the program name) must have the double quotes if there is a space in
        # self.gms_program.
        
        #print  "program = ", program
        #print  "Spawnv args are %r" % (args,) # this %r remains (see above)
        #os.spawnv(os.P_WAIT, self.job_batfile, args)
        
        arg_list = ['-i', self.job_inputfile, '-o', self.job_outputfile]
        args = QStringList()        
        for s in arg_list:
            args.append(str(s))          
            
        process = QProcess()
        process.start(self.server.program, args)
        # Blocks for n millisconds until the process has started and started() 
        # signal is emitted. Returns true if the process was started successfullly. 
        if not process.waitForStarted(2000): 
            print "The process can't be started."
            return 2
        progressDialog = self.showProgress()
        progressDialog.show()
        i = 55
        pInc = True
        while process.state() == QProcess.Running:
            env.call_qApp_processEvents() #bruce 050908 replaced qApp.processEvents()
            if progressDialog.wasCanceled():
                process.kill()
                os.chdir(oldir)
                return 1 # Job cancelled.
                           
            progressDialog.setValue(i)
            if pInc:
                if i < 75: i += 1
                else: pInc = False
            else:
                if i > 55:  i -= 1
                else: pInc = True
            # Do sth here
            time.sleep(0.05)
            if not process.state() == QProcess.Running:
                 break
              
        progressDialog.setValue(100)
        progressDialog.accept()
        
        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile
        
        return 0 # Success
Ejemplo n.º 13
0
    def show_progressbar_and_stop_button(self,
                                         progressReporter,
                                         cmdname="<unknown command>",
                                         showElapsedTime=False):
        """
        Display the statusbar's progressbar and stop button, and
        update it based on calls to the progressReporter.

        When the progressReporter indicates completion, hide the
        progressbar and stop button and return 0. If the user first
        presses the Stop button on the statusbar, hide the progressbar
        and stop button and return 1.

        Parameters:
        progressReporter - See potential implementations below.
        cmdname - name of command (used in some messages and in abort button tooltip)
        showElapsedTime - if True, display duration (in seconds) below progress bar

        Return value: 0 if file reached desired size, 1 if user hit abort button.

        """

        updateInterval = .1  # seconds
        startTime = time.time()
        elapsedTime = 0
        displayedElapsedTime = 0

        ###e the following is WRONG if there is more than one task at a time... [bruce 060106 comment]
        self.progressBar.reset()
        self.progressBar.setMaximum(progressReporter.getMaxProgress())
        self.progressBar.setValue(0)
        self.progressBar.show()

        abortHandler = AbortHandler(self, cmdname)

        # Main loop
        while progressReporter.notDoneYet():
            self.progressBar.setValue(progressReporter.getProgress())
            env.call_qApp_processEvents()
            # Process queued events (e.g. clicking Abort button,
            # but could be anything -- no modal dialog involved anymore).

            if showElapsedTime:
                elapsedTime = int(time.time() - startTime)
                if (elapsedTime != displayedElapsedTime):
                    displayedElapsedTime = elapsedTime
                    env.history.progress_msg("Elapsed Time: " +
                                             hhmmss_str(displayedElapsedTime))
                    # note: it's intentional that this doesn't directly call
                    # self._f_progress_msg. [bruce 081229 comment]

            if abortHandler.getPressCount() > 0:
                env.history.statusbar_msg("Aborted.")
                abortHandler.finish()
                return 1

            time.sleep(updateInterval)  # Take a rest

        # End of Main loop (this only runs if it ended without being aborted)
        self.progressBar.setValue(progressReporter.getMaxProgress())
        time.sleep(
            updateInterval)  # Give the progress bar a moment to show 100%
        env.history.statusbar_msg("Done.")
        abortHandler.finish()
        return 0
Ejemplo n.º 14
0
    def _launch_pcgamess(self):
        """
        Run PC GAMESS (Windows only).
        PC GAMESS creates 2 output files:
          - the DAT file, called "PUNCH", is written to the directory from which
            PC GAMESS is started.  This is why we chdir to the Gamess temp directory
            before we run PC GAMESS.
          - the OUT file (aka the log file), which we name jigname.out.
        Returns: 0 = Success
                       1 = Cancelled
                       2 = Failed
        """
        oldir = os.getcwd()  # Save current directory

        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir)  # Change directory to the GAMESS temp directory.
        ##        print "Current directory is: ", jobDir

        DATfile = os.path.join(jobDir, "PUNCH")
        if os.path.exists(DATfile):  # Remove any previous DAT (PUNCH) file.
            print "run_pcgamess: Removing DAT file: ", DATfile
            os.remove(DATfile)

        # Hours wasted testing this undocumented tripe.  Here's the deal: When using spawnv
        # on Windows, any args that might have spaces must be delimited by double quotes.
        # Mark 050530.

        #program = "\"" + self.job_batfile + "\""
        #args = [program, ]

        # Here's the infuriating part.  The 2nd arg to spawnv cannot have double quotes, but the
        # first arg in args (the program name) must have the double quotes if there is a space in
        # self.gms_program.

        #print  "program = ", program
        #print  "Spawnv args are %r" % (args,) # this %r remains (see above)
        #os.spawnv(os.P_WAIT, self.job_batfile, args)

        arg_list = ['-i', self.job_inputfile, '-o', self.job_outputfile]
        args = QStringList()
        for s in arg_list:
            args.append(str(s))

        process = QProcess()
        process.start(self.server.program, args)
        # Blocks for n millisconds until the process has started and started()
        # signal is emitted. Returns true if the process was started successfullly.
        if not process.waitForStarted(2000):
            print "The process can't be started."
            return 2
        progressDialog = self.showProgress()
        progressDialog.show()
        i = 55
        pInc = True
        while process.state() == QProcess.Running:
            env.call_qApp_processEvents(
            )  #bruce 050908 replaced qApp.processEvents()
            if progressDialog.wasCanceled():
                process.kill()
                os.chdir(oldir)
                return 1  # Job cancelled.

            progressDialog.setValue(i)
            if pInc:
                if i < 75: i += 1
                else: pInc = False
            else:
                if i > 55: i -= 1
                else: pInc = True
            # Do sth here
            time.sleep(0.05)
            if not process.state() == QProcess.Running:
                break

        progressDialog.setValue(100)
        progressDialog.accept()

        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile

        return 0  # Success
Ejemplo n.º 15
0
def launch_povray_or_megapov(
        win, info,
        povray_ini):  #bruce 060707/11 revised this extensively for Mac A8
    """
    Try to launch POV-Ray or MegaPOV, as specified in <info> (as returned from decode_povray_prefs, assumed already checked),
    on the given <povray_ini> file (which should already exist), and running in the directory of that file
    (this is required, since it may contain relative pathnames).
    <win> must be the main window object (used for .glpane.is_animating).
       Returns (errorcode, errortext), where errorcode is one of the following: ###k
        0 = successful
        8 = POV-Ray or MegaPOV failed for an unknown reason.
    """
    (program_nickname, program_path,
     include_dir) = info  #e rename this arg renderer_info?

    exit = ''
    program = program_path

    if sys.platform == 'win32':
        program = "\"" + program + "\""  # Double quotes needed by Windows. Mark 060602.
        if program_nickname == 'POV-Ray':
            exit = "/EXIT"

    # Later we'll cd to the POV-Ray's INI file directory and use tmp_ini in the POV-Ray command-line.
    # This helps us get around POV-Ray's I/O Restrictions. Mark 060529.
    workdir, tmp_ini = os.path.split(povray_ini)

    # Render scene.
    try:
        args = [tmp_ini]
        if exit:
            args += [exit]
        if env.debug():
            ## use env.history.message(_graymsg(msg)) ?
            print "debug: Launching %s: \n" % program_nickname,\
                  "working directory=",workdir,"\n  program_path=", program_path,  "\n  args are %r" % (args,)

        arguments = QStringList()
        for arg in args:
            if arg != "":
                arguments.append(arg)

        from processes.Process import Process
        p = Process()
        #bruce 060707: this doesn't take advantage of anything not in QProcess,
        # unless it matters that it reads and discards stdout/stderr
        # (eg so large output would not block -- unlikely that this matters).
        # It doesn't echo stdout/stderr. See also blabout/blaberr in other files. Maybe fix this? ###@@@
        p.setWorkingDirectory(workdir)
        p.start(program, arguments)

        # Put up hourglass cursor to indicate we are busy. Restore the cursor below. Mark 060621.
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

        win.glpane.is_animating = True  # This disables selection [do you mean highlighting? #k] while rendering the image.

        import time
        msg = "Rendering image"
        while p.state() == QProcess.Running:
            # Display a message on the status bar that POV-Ray/MegaPOV is rendering.
            # I'd much rather display a progressbar and stop button by monitoring the size of the output file.
            # This would require the output file to be written in PPM or BMP format, but not PNG format, since
            # I don't believe a PNG's final filesize can be predicted.
            # Check out monitor_progress_by_file_growth() in runSim.py, which does this. [mark]
            time.sleep(0.25)
            env.history.statusbar_msg(msg)
            env.call_qApp_processEvents()
            if 1:
                # Update the statusbar message while rendering.
                if len(
                        msg
                ) > 40:  #bruce changed 100 -> 40 in case of short statusbar
                    msg = "Rendering image"
                else:
                    #msg = msg + "."
                    msg += "."

    except:
        #bruce 060707 moved print_compact_traceback earlier, and its import to toplevel (after Windows A8, before Linux/Mac A8)
        print_compact_traceback("exception in launch_povray_or_megapov(): ")
        QApplication.restoreOverrideCursor()
        win.glpane.is_animating = False
        return 8, "%s failed for an unknown reason." % program_nickname

    #bruce 060707 moved the following outside the above try clause, and revised it (after Windows A8, before Linux/Mac A8)
    QApplication.restoreOverrideCursor()  # Restore the cursor. Mark 060621.
    ## env.history.statusbar_msg("Rendering finished!") # this is wrong if it was not a normal exit. [bruce 060707 removed it]
    win.glpane.is_animating = False

    if 1:
        #bruce 060707 added this (after Windows A8, before Linux/Mac A8):
        # set an appropriate exitcode and msg
        if p.exitStatus() == QProcess.NormalExit:
            exitcode = p.exitStatus()
            if not exitcode:
                msg = "Rendering finished!"
            else:
                msg = "Rendering program had exitcode %r" % exitcode
                # e.g. 126 for Mac failure; same as shell exitcode, which says "cannot execute binary file";
                # but /usr/bin/open helps, so we'll try that above (but not in this commit, which is just to
                # improve error reporting). ###@@@
                # [bruce 060707]
        else:
            exitcode = p.exitStatus()
            exitcode = -1
            msg = "Abnormal exit (or failure to launch)"
        if exitcode or env.debug():
            print msg
        env.history.statusbar_msg(msg)
        ##        if env.debug():
        ##            env.history.message(_graymsg(msg)) # not needed, caller prints it
        if exitcode:
            return 8, "Error: " + msg  # this breaks the convention of the other error returns
        pass

    # Display image in separate window here. [Actually I think this is done in the caller -- bruce 060707 comment]

    return 0, "Rendering finished"  # from launch_povray_or_megapov
Ejemplo n.º 16
0
def launch_povray_or_megapov(win, info, povray_ini): #bruce 060707/11 revised this extensively for Mac A8
    """
    Try to launch POV-Ray or MegaPOV, as specified in <info> (as returned from decode_povray_prefs, assumed already checked),
    on the given <povray_ini> file (which should already exist), and running in the directory of that file
    (this is required, since it may contain relative pathnames).
    <win> must be the main window object (used for .glpane.is_animating).
       Returns (errorcode, errortext), where errorcode is one of the following: ###k
        0 = successful
        8 = POV-Ray or MegaPOV failed for an unknown reason.
    """
    (program_nickname, program_path, include_dir) = info #e rename this arg renderer_info?

    exit = ''
    program = program_path

    if sys.platform == 'win32':
        program = "\""+program+"\"" # Double quotes needed by Windows. Mark 060602.
        if program_nickname == 'POV-Ray':
            exit = "/EXIT"

    # Later we'll cd to the POV-Ray's INI file directory and use tmp_ini in the POV-Ray command-line.
    # This helps us get around POV-Ray's I/O Restrictions. Mark 060529.
    workdir, tmp_ini = os.path.split(povray_ini)

    # Render scene.
    try:
        args = [tmp_ini]
        if exit:
            args += [exit]
        if env.debug():
            ## use env.history.message(_graymsg(msg)) ?
            print "debug: Launching %s: \n" % program_nickname,\
                  "working directory=",workdir,"\n  program_path=", program_path,  "\n  args are %r" % (args,)

        arguments = QStringList()
        for arg in args:
            if arg != "":
                arguments.append(arg)

        from processes.Process import Process
        p = Process()
            #bruce 060707: this doesn't take advantage of anything not in QProcess,
            # unless it matters that it reads and discards stdout/stderr
            # (eg so large output would not block -- unlikely that this matters).
            # It doesn't echo stdout/stderr. See also blabout/blaberr in other files. Maybe fix this? ###@@@
        p.setWorkingDirectory(workdir)
        p.start(program, arguments)

        # Put up hourglass cursor to indicate we are busy. Restore the cursor below. Mark 060621.
        QApplication.setOverrideCursor( QCursor(Qt.WaitCursor) )

        win.glpane.is_animating = True # This disables selection [do you mean highlighting? #k] while rendering the image.

        import time
        msg = "Rendering image"
        while p.state() == QProcess.Running:
            # Display a message on the status bar that POV-Ray/MegaPOV is rendering.
            # I'd much rather display a progressbar and stop button by monitoring the size of the output file.
            # This would require the output file to be written in PPM or BMP format, but not PNG format, since
            # I don't believe a PNG's final filesize can be predicted.
            # Check out monitor_progress_by_file_growth() in runSim.py, which does this. [mark]
            time.sleep(0.25)
            env.history.statusbar_msg(msg)
            env.call_qApp_processEvents()
            if 1:
                # Update the statusbar message while rendering.
                if len(msg) > 40: #bruce changed 100 -> 40 in case of short statusbar
                    msg = "Rendering image"
                else:
                    #msg = msg + "."
                    msg += "."

    except:
        #bruce 060707 moved print_compact_traceback earlier, and its import to toplevel (after Windows A8, before Linux/Mac A8)
        print_compact_traceback( "exception in launch_povray_or_megapov(): " )
        QApplication.restoreOverrideCursor()
        win.glpane.is_animating = False
        return 8, "%s failed for an unknown reason." % program_nickname

    #bruce 060707 moved the following outside the above try clause, and revised it (after Windows A8, before Linux/Mac A8)
    QApplication.restoreOverrideCursor() # Restore the cursor. Mark 060621.
    ## env.history.statusbar_msg("Rendering finished!") # this is wrong if it was not a normal exit. [bruce 060707 removed it]
    win.glpane.is_animating = False

    if 1:
        #bruce 060707 added this (after Windows A8, before Linux/Mac A8):
        # set an appropriate exitcode and msg
        if p.exitStatus() == QProcess.NormalExit:
            exitcode = p.exitStatus()
            if not exitcode:
                msg = "Rendering finished!"
            else:
                msg = "Rendering program had exitcode %r" % exitcode
                    # e.g. 126 for Mac failure; same as shell exitcode, which says "cannot execute binary file";
                    # but /usr/bin/open helps, so we'll try that above (but not in this commit, which is just to
                    # improve error reporting). ###@@@
                    # [bruce 060707]
        else:
            exitcode = p.exitStatus()
            exitcode = -1
            msg = "Abnormal exit (or failure to launch)"
        if exitcode or env.debug():
            print msg
        env.history.statusbar_msg(msg)
##        if env.debug():
##            env.history.message(_graymsg(msg)) # not needed, caller prints it
        if exitcode:
            return 8, "Error: " + msg # this breaks the convention of the other error returns
        pass

    # Display image in separate window here. [Actually I think this is done in the caller -- bruce 060707 comment]

    return 0, "Rendering finished" # from launch_povray_or_megapov