| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
| |
| <html xmlns="http://www.w3.org/1999/xhtml"> |
| |
| <head> |
| |
| <title>Writing POV-Ray Support for NetBeans VII—Support For Running POV-Ray</title> |
| |
| <link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/> |
| <meta name="AUDIENCE" content="NBUSER"/> |
| <meta name="TYPE" content="ARTICLE"/> |
| <meta name="EXPIRES" content="N"/> |
| <meta name="developer" content="geertjan.wielenga@oracle.com"/> |
| <meta name="indexed" content="y"/> |
| <meta name="description" |
| content="NetBeans POV-Ray Support Tutorial Part VII—How to launch an external process and monitor its output in the output window"/> |
| <!-- Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. --> |
| <!-- Use is subject to license terms.--> |
| |
| </head> |
| |
| <body> |
| |
| <h1>Writing POV-Ray Support for NetBeans VII—Support For Running POV-Ray</h1> |
| |
| <p>This is a continuation of the tutorial for building a POV-Ray rendering application on |
| the NetBeans Platform. If you have not read the <a href="nbm-povray-1.html">first</a>, |
| <a href="nbm-povray-2.html">second</a>, <a href="nbm-povray-3.html">third</a>, |
| <a href="nbm-povray-4.html">fourth</a>, <a href="nbm-povray-5.html">fifth</a>, and |
| <a href="nbm-povray-6.html">sixth</a> |
| parts of this tutorial, you may want to start there.</p> |
| |
| <h2 class="tutorial"><a name="executing"></a>Obtaining POV-Ray</h2> |
| |
| <p>At this point, we're almost ready to run code, so it would be a good idea |
| to download a copy of POV-Ray. </p> |
| <ul> |
| <li>Official builds can be obtained from |
| <a href="http://povray.org">povray.org</a>. |
| <!-- but there is a caveat—the |
| Windows version of POV-Ray is a GUI executable, which will want to open |
| an editor rather than render your file to disk. So Windows users should |
| download an "unofficial" build such as |
| <a href="http://www.imagico.de/files/povcyg_350c.zip">this one</a>, or create |
| a command-line build using cygwin and gcc.--> |
| </li> |
| <li>Macintosh users may find |
| <a href="http://darwinports.opendarwin.org/">DarwinPorts</a> the easiest |
| way—simply install DarwinPorts and then run |
| <code>sudo port install povray</code>.</li> |
| <li>Linux and other Unix users should be |
| fine with the downloads available from <a href="http://povray.org">povray.org</a>, |
| using <a href="http://povray.org/download/linux.php">these instructions</a>. |
| Everything should work out of the box for these users, without any |
| tweaks or post-install configurations. Make sure, however, that the |
| POV-Ray launcher has the correct permissions, otherwise it will not |
| execute. Run it from the command line to check that it executes.</li> |
| </ul> |
| |
| <p class="notes"><b>Note:</b> For details on setting up POV-Ray, especially |
| for detailed instructions for Windows, <a href="nbm-povray-10.html#appendix">see the Appendix</a>.</p> |
| |
| <h2 class="tutorial"><a name="setup"></a>Executing POV-Ray and Displaying the Output</h2> |
| |
| <p>At this point, we are ready to write the code that will actually invoke the |
| external POV-Ray executable, pass it the correct arguments and display its |
| output.</p> |
| |
| <p>At the end of this part of the tutorial, you will have an application |
| that looks like this (click to enlarge the image):</p> |
| |
| <p><a href="../../images/tutorials/povray/71/ch7/pic1.png"><img alt="" src="../../images/tutorials/povray/71/ch7/pic2.png"/></a></p> |
| |
| |
| <p>POV-Ray has two kinds of output: It will write out status and |
| success failure on the command line, in a similar way to what a compiler |
| does, and it will write out an image file on disk, which we will want to |
| display.</p> |
| |
| <p>The first part is just being able to invoke the external executable. |
| NetBeans has some API classes that can help with that.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li><p>Add the following constructor and fields to the <code>Povray</code> |
| class:</p> |
| <pre class="examplecode"> private final RendererServiceImpl renderService; |
| private final FileObject toRender; |
| private final Properties settings; |
| |
| Povray (RendererServiceImpl renderService, FileObject toRender, Properties settings) { |
| this.renderService = renderService; |
| this.toRender = toRender; |
| this.settings = settings == null ? renderService.getRendererSettings( |
| renderService.getPreferredRendererSettingsName()) : settings; |
| }</pre> |
| </li> |
| |
| <li><p>Next we will implement a method that will find the file to render. |
| We were passed a <code>FileObject</code>, but now we need an actual |
| <code>java.io.File</code> to get the path from, to pass on the command |
| line to POV-Ray. There are two caveats: 1. The file passed to the |
| constructor may be null—in that case we should find the main file of |
| the project and use that; and 2. It is conceivable that the file will |
| not exist on disk—NetBeans filesystems are virtual, after all, and |
| the file could exist in a remote FTP filesystem or such. Since NetBeans |
| 4.0, this is rather unlikely, but we should still test for this condition |
| (<code>FileUtil.toFile()</code> returns null). So we will add a method |
| to <code>Povray</code> as follows:</p> |
| |
| <pre class="examplecode">private File getFileToRender() throws IOException { |
| FileObject render = toRender; |
| if (render == null) { |
| PovrayProject proj = renderService.getProject(); |
| MainFileProvider provider = (MainFileProvider) |
| proj.getLookup().lookup (MainFileProvider.class); |
| if (provider == null) { |
| throw new IllegalStateException ("Main file provider missing"); |
| } |
| render = provider.getMainFile(); |
| if (render == null) { |
| ProjectInformation info = (ProjectInformation) |
| proj.getLookup().lookup(ProjectInformation.class); |
| |
| //XXX let the user choose |
| throw new IOException (NbBundle.getMessage(Povray.class, |
| "MSG_NoMainFile", info.getDisplayName())); |
| } |
| } |
| assert render != null; |
| File result = FileUtil.toFile (render); |
| if (result == null) { |
| throw new IOException (NbBundle.getMessage(Povray.class, |
| "MSG_VirtualFile", render.getName())); |
| } |
| assert result.exists(); |
| assert result.isFile(); |
| return result; |
| }</pre> |
| |
| </li> |
| |
| <li><p>Next we need to assemble the command-line arguments that need to |
| be passed to POV-Ray. These take the form of |
| <code>+[some character][somevalue]</code>, for example, |
| <code>+A0.9</code> sets the anti-aliasing parameter to 0.9 pixels. |
| So we need to iterate the <code>Properties</code> object passed to the |
| constructor and assemble from it a set of command line arguments:</p> |
| |
| <pre class="examplecode">private String getCmdLineArgs(File includesDir) { |
| StringBuilder cmdline = new StringBuilder(); |
| for (Iterator i=settings.keySet().iterator(); i.hasNext();) { |
| String key = (String) i.next(); |
| String val = settings.getProperty(key); |
| cmdline.append ('+'); |
| cmdline.append (key); |
| cmdline.append (val); |
| cmdline.append (' '); |
| } |
| cmdline.append ("+L"); |
| cmdline.append (includesDir.getPath()); |
| return cmdline.toString(); |
| }</pre> |
| |
| </li> |
| |
| <li><p>Next we need to implement a couple of utility methods that the |
| rendering method will use:</p> |
| |
| <pre class="examplecode">private File getImagesDir() { |
| PovrayProject proj = renderService.getProject(); |
| FileObject fob = proj.getImagesFolder(true); |
| File result = FileUtil.toFile(fob); |
| assert result != null && result.exists(); |
| return result; |
| } |
| |
| private String stripExtension(File f) { |
| String sceneName = f.getName(); |
| int endIndex; |
| if ((endIndex = sceneName.lastIndexOf('.')) != -1) { |
| sceneName = sceneName.substring(0, endIndex); |
| } |
| return sceneName; |
| }</pre> |
| |
| <p>Neither is terribly exciting—one gets the images directory from the |
| project as a <code>java.io.File</code>, and the other trims the file |
| extension off a file name (so we can create an image file with the same |
| name as the scene file).</p> |
| |
| </li> |
| |
| <li><p>The next method we will add is another utility method. When we |
| render, we will want to show messages on the status bar that describe |
| what is happening—or what went wrong in the event of failure. The |
| UI Utilities API contains a class called <code>StatusDisplayer</code> |
| that lets any code in NetBeans that wants to write to the status bar |
| (the actual implementation of <code>StatusDisplayer</code> is in the |
| windowing system implementations, <code>core/windows</code> in NetBeans |
| CVS).</p> |
| |
| <p>Implement the following method, and then add a dependency on the |
| UI Utilities API module from the Povray Projects module:</p> |
| |
| <pre class="examplecode">private void showMsg (String msg) { |
| StatusDisplayer.getDefault().setStatusText(msg); |
| }</pre> |
| |
| </li> |
| |
| <li><p>At this point, we've added a bunch of status messages our code can |
| display, so it is time to add actual text for those messages to the |
| resource bundle. Note that in a number of cases we call:</p> |
| |
| <pre class="examplecode">NbBundle.getMessage(SomeClass.class, "MSG_Something", <i><u>someStringArgument</u></i>);</pre> |
| |
| <p>to fetch a localized string. <code>NbBundle</code> supports embedding |
| arguments inside of a localized string—you can either use the above |
| method, or a variant that takes an array of arguments to embed. So you |
| can define strings in a resource bundle using the syntax:</p> |
| |
| <pre class="examplecode">Could not delete {0} because {1}</pre> |
| |
| <p>and <code>{0}</code> and <code>{1}</code> will be replaced by arguments |
| passed to <code>getMessage()</code>. This is extremely useful, as often |
| the order in which such strings occur in the result text will be different |
| in different human languages.</p> |
| |
| <p>So let's go ahead and add the warning messages we need to |
| <code>Bundle.properties</code> in the same package as <code>PovrayProject</code>: |
| <pre class="examplecode">MSG_NoMainFile=Main scene file not set for {0} |
| MSG_VirtualFile=Not a file on disk: {0} |
| MSG_Rendering=Rendering {0} |
| MSG_NoPovrayExe=No POV-Ray executable, cannot render |
| MSG_NoPovrayInc=No POV-Ray includes dir, cannot render |
| MSG_Success=Rendered {0} successfully |
| MSG_Failure=Failed to render {0} |
| MSG_CantDelete=Could not delete {0}, it is locked or in use</pre> |
| |
| </p></li> |
| |
| <li><p>Now we are almost ready to get down to the nitty-gritty of actually |
| invoking POV-Ray from NetBeans. We will do this in the standard Java |
| way, using <code>Runtime.exec()</code> to start an external process. |
| We also will want to display the text output from the process as it |
| reports its progress, in the output window. This means we will need a |
| way to write to the output window. So we will add one more dependency |
| to Povray Projects—add a dependency on the IO API module (use the class |
| name <code>InputOutput</code> in the Add Dependency dialog).</p> |
| </li> |
| |
| <li><p>Handling output from a process is tricky—we will actually have |
| three threads running to handle our process:</p> |
| |
| <ul> |
| <li>The thread that invoked the process and is waiting for it to |
| terminate</li> |
| <li>A thread that is collecting output from the standard output of |
| the POV-Ray process and writing it to the output window</li> |
| <li>Another thread that is doing the same thing for the error output |
| of the POV-Ray process</li> |
| </ul> |
| |
| <p>So we will need some kind of <code>Runnable</code> which will wait for |
| data from each output stream and route it to the output window in |
| NetBeans as it becomes available. Writing to the output window is quite |
| easy—you get an <code>InputOutput</code> object from |
| <code>IOProvider.getDefault()</code> and then write to one of its streams—for |
| example:</p> |
| |
| <pre class="examplecode"> InputOutput io = IOProvider.getDefault().getIO ("Hello", true); |
| io.select(); |
| io.getOut().println ("Hello world"); |
| io.getErr().println ("This is the standard error output—it should be red");</pre> |
| |
| <p>is all it takes to make the output window pop up and display some output.</p> |
| |
| <p>So before we implement the code that will create the process, lets create |
| the runnable that will wait for output from the process and route it to |
| the output window—it will be a static nested class inside the |
| <code>Povray</code> class:</p> |
| |
| <pre class="examplecode"> static class OutHandler implements Runnable { |
| |
| private Reader out; |
| |
| private OutputWriter writer; |
| |
| public OutHandler (Reader out, OutputWriter writer) { |
| this.out = out; |
| this.writer = writer; |
| } |
| |
| @Override |
| public void run() { |
| while (true) { |
| try { |
| while (!out.ready()) { |
| try { |
| Thread.currentThread().sleep(200); |
| } catch (InterruptedException e) { |
| close(); |
| return; |
| } |
| } |
| if (!readOneBuffer() || Thread.currentThread().isInterrupted()) { |
| close(); |
| return; |
| } |
| } catch (IOException ioe) { |
| //Stream already closed, this is fine |
| return; |
| } |
| } |
| } |
| |
| private boolean readOneBuffer() throws IOException { |
| char[] cbuf = new char[255]; |
| int read; |
| while ((read = out.read(cbuf)) != -1) { |
| writer.write(cbuf, 0, read); |
| } |
| return read != -1; |
| } |
| |
| private void close() { |
| try { |
| out.close(); |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } finally { |
| writer.close(); |
| } |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| <li><p>Now we are ready to implement the <code>render()</code> method in the |
| <tt>Povray</tt> class, in the Povray Project module, that |
| will invoke <code>POV-Ray</code>. This method should be never be invoked from |
| the event thread, because it would block the UI until POV-Ray is finished. |
| So the first thing we do is sanity check what thread we're running on. |
| Then we get the file we need to render, sanity checking that. Then we |
| call <code>getPovray()</code> which may open a file chooser to let the |
| user pick it, and similarly get the default include directory which we |
| will need to pass on the command line. Then we get the directory where |
| we will put the output, assemble our output file name (we use PNG format |
| since NetBeans' Image module supports that).</p> |
| <p>Then we compute the command |
| line that should be passed to POV-Ray. Then we call |
| <code>Runtime.exec()</code> with that argument, wire up the output window |
| to the output streams from the resulting process, and wait for the |
| process to exit. Once it exits, we determine if it succeeded or failed, |
| show an appropriate message, and if it succeeded, return a |
| <code>FileObject</code> representing the file that was created.</p> |
| |
| <pre class="examplecode">public FileObject render () throws IOException { |
| |
| if (EventQueue.isDispatchThread()) { |
| throw new IllegalStateException ("Tried to run povray from the " + |
| "event thread"); |
| } |
| |
| //Find the scene file pass to POV-Ray as a java.io.File |
| File scene; |
| try { |
| scene = getFileToRender(); |
| } catch (IOException ioe) { |
| showMsg (ioe.getMessage()); |
| return null; |
| } |
| |
| //Get the POV-Ray executable |
| File povray = getPovray(); |
| if (povray == null) { |
| //The user cancelled the file chooser w/o selecting |
| showMsg(NbBundle.getMessage(Povray.class, "MSG_NoPovrayExe")); |
| return null; |
| } |
| |
| //Get the include dir, if it isn't under povray's home dir |
| File includesDir = getStandardIncludeDir(povray); |
| if (includesDir == null) { |
| //The user cancelled the file chooser w/o selecting |
| showMsg (NbBundle.getMessage(Povray.class, "MSG_NoPovrayInc")); |
| return null; |
| } |
| |
| //Find the image output directory for the project |
| File imagesDir = getImagesDir(); |
| |
| //Assemble and format the line switches for the POV-Ray process based |
| //on the contents of the Properties object |
| String args = getCmdLineArgs(includesDir); |
| String outFileName = stripExtension (scene) + ".png"; |
| |
| //Compute the name of the output image file |
| File outFile = new File(imagesDir.getPath() + File.separator + |
| outFileName); |
| |
| //Delete the image if it exists, so that any current tab viewing the file is |
| //closed and the file will definitely be re-read when it is re-opened |
| if (outFile.exists() && !outFile.delete()) { |
| showMsg (NbBundle.getMessage(Povray.class, |
| "LBL_CantDelete", outFile.getName())); |
| return null; |
| } |
| |
| //Append the input file and output file arguments to the command line |
| String cmdline = povray.getPath() + ' ' + args + " +I" + |
| scene.getPath() + " +O" + outFile.getPath(); |
| |
| System.err.println(cmdline); |
| |
| showMsg (NbBundle.getMessage(Povray.class, "MSG_Rendering", |
| scene.getName())); |
| final Process process = Runtime.getRuntime().exec (cmdline); |
| |
| //Get the standard out of the process |
| InputStream out = new BufferedInputStream (process.getInputStream(), 8192); |
| |
| //Get the standard in of the process |
| InputStream err = new BufferedInputStream (process.getErrorStream(), 8192); |
| |
| //Create readers for each |
| final Reader outReader = new BufferedReader (new InputStreamReader (out)); |
| final Reader errReader = new BufferedReader (new InputStreamReader (err)); |
| |
| //Get an InputOutput to write to the output window |
| InputOutput io = IOProvider.getDefault().getIO(scene.getName(), false); |
| |
| //Force it to open the output window/activate our tab |
| io.select(); |
| |
| //Print the command line we're calling for debug purposes |
| io.getOut().println(cmdline); |
| |
| //Create runnables to poll each output stream |
| OutHandler processSystemOut = new OutHandler (outReader, io.getOut()); |
| OutHandler processSystemErr = new OutHandler (errReader, io.getErr()); |
| |
| //Get two different threads listening on the output & err |
| //using the system-wide thread pool |
| RequestProcessor.getDefault().post(processSystemOut); |
| RequestProcessor.getDefault().post(processSystemErr); |
| |
| try { |
| //Hang this thread until the process exits |
| process.waitFor(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| //Close the output window's streams (title will become non-bold) |
| processSystemOut.close(); |
| processSystemErr.close(); |
| |
| if (outFile.exists() && process.exitValue() == 0) { |
| //Try to find the new image file |
| FileObject outFileObject = FileUtil.toFileObject(outFile); |
| showMsg (NbBundle.getMessage(Povray.class, "MSG_Success", |
| outFile.getPath())); |
| return outFileObject; |
| } else { |
| showMsg (NbBundle.getMessage(Povray.class, "MSG_Failure", |
| scene.getPath())); |
| return null; |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| <li><p>The last thing is to fix our implementation of |
| <code>RendererService</code> to call <code>Povray.render()</code>. |
| Open <code>RendererServiceImpl</code> in the code editor, and modify |
| the render method:</p> |
| |
| <pre class="examplecode">@Override |
| public FileObject render(FileObject scene, Properties renderSettings) { |
| Povray pov = new Povray (this, scene, renderSettings); |
| try { |
| return pov.render(); |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| return null; |
| } |
| }</pre> |
| |
| </li> |
| |
| <li><p>The last step is to open the image when the rendering process is |
| complete. This is quite simple to implement—we just need to look |
| for an <code>OpenCookie</code> on the <code>Node</code> for the image |
| that was rendered. If you are running a standard configuration of the |
| NetBeans IDE, you already have the Image module installed—it will |
| provide support for opening an image, displaying it in the editor area. |
| So implement <code>RendererAction.RenderWithSettingsAction.run()</code> like |
| this:</p> |
| |
| <pre class="examplecode">public void run() { |
| DataObject ob = node.getDataObject(); |
| FileObject toRender = ob.getPrimaryFile(); |
| <b>Properties mySettings = renderer.getRendererSettings(name);</b> |
| FileObject image = renderer.render(toRender, <b>mySettings</b>); |
| if (image != null) { |
| try { |
| DataObject dob = DataObject.find (image); |
| Node n = dob.getNodeDelegate(); |
| OpenCookie ck = (OpenCookie) n.getLookup().lookup(OpenCookie.class); |
| if (ck != null) { |
| ck.open(); |
| } |
| } catch (DataObjectNotFoundException e) { |
| //Should never happen |
| Exceptions.printStackTrace(e); |
| } |
| } |
| }</pre> |
| |
| </ol> |
| |
| </div> |
| |
| <p>With that, you should be able to clean, build and run the application and |
| be able to run POV-Ray and generate an image in the <code>images/</code> |
| subdirectory of your project:</p> |
| |
| <p><a href="../../images/tutorials/povray/71/ch7/pic1.png"><img alt="" src="../../images/tutorials/povray/71/ch7/pic2.png"/></a></p> |
| |
| <h2 class="tutorial"><a name="next"></a>Next Steps</h2> |
| |
| <p>The <a href="nbm-povray-8.html">next section</a> will cover implementing |
| <code>ViewService</code> and adding actions for that.</p> |
| |
| </body> |
| |
| </html> |